@cryptotaxi247 / CoPilot / commits / 86fc5150

rm backend folder

Davide Di Modica committed Oct 16, 2023 at 17:59 UTC 86fc5150b25df104f3c0859747c1fb5bc9ce1d2c
114 files changed -9695
backend/app/agents/routes/agents.py deleted
-120
@@ -1,120 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import HTTPException
3 -from loguru import logger
4 -from starlette.status import HTTP_401_UNAUTHORIZED
5 -
6 -from app.agents.schema.agents import AgentModifyResponse
7 -from app.agents.schema.agents import AgentsResponse
8 -from app.agents.schema.agents import AgentUpdateCustomerCodeBody
9 -from app.agents.schema.agents import AgentUpdateCustomerCodeResponse
10 -from app.agents.schema.agents import OutdatedVelociraptorAgentsResponse
11 -from app.agents.schema.agents import OutdatedWazuhAgentsResponse
12 -from app.agents.schema.agents import SyncedAgentsResponse
13 -from app.agents.services.modify import mark_agent_criticality
14 -from app.agents.services.status import get_outdated_agents_velociraptor
15 -from app.agents.services.status import get_outdated_agents_wazuh
16 -from app.agents.services.sync import sync_agents
17 -from app.agents.velociraptor.services.agents import delete_agent_velociraptor
18 -from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilitiesResponse
19 -from app.agents.wazuh.services.vulnerabilities import collect_agent_vulnerabilities
20 -
21 -# App specific imports
22 -from app.db.db_session import session
23 -from app.db.universal_models import Agents
24 -
25 -agents_router = APIRouter()
26 -
27 -
28 -def verify_admin(user):
29 - if not user.is_admin:
30 - raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Unauthorized")
31 -
32 -
33 -@agents_router.get("", response_model=AgentsResponse, description="Get all disabled rules")
34 -async def get_agents() -> AgentsResponse:
35 - logger.info("Fetching all agents")
36 - agents = session.query(Agents).all()
37 - return AgentsResponse(agents=agents, success=True, message="Agents fetched successfully")
38 -
39 -
40 -@agents_router.get("/{agent_id}", response_model=AgentsResponse, description="Get agent by agent_id")
41 -async def get_agent(agent_id: str) -> AgentsResponse:
42 - logger.info(f"Fetching agent with agent_id: {agent_id}")
43 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
44 - if not agent:
45 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
46 - return AgentsResponse(agents=[agent], success=True, message="Agent fetched successfully")
47 -
48 -
49 -@agents_router.get("/hostname/{hostname}", response_model=AgentsResponse, description="Get agent by hostname")
50 -async def get_agent_by_hostname(hostname: str) -> AgentsResponse:
51 - logger.info(f"Fetching agent with hostname: {hostname}")
52 - agent = session.query(Agents).filter(Agents.hostname == hostname).first()
53 - if not agent:
54 - raise HTTPException(status_code=404, detail=f"Agent with hostname {hostname} not found")
55 - return AgentsResponse(agents=[agent], success=True, message="Agent fetched successfully")
56 -
57 -
58 -@agents_router.post("/sync", response_model=SyncedAgentsResponse, description="Sync agents from Wazuh Manager")
59 -async def sync_all_agents() -> SyncedAgentsResponse:
60 - logger.info("Syncing agents from Wazuh Manager")
61 - return sync_agents()
62 -
63 -
64 -@agents_router.post("/{agent_id}/critical", response_model=AgentModifyResponse, description="Mark agent as critical")
65 -async def mark_agent_as_critical(agent_id: str) -> AgentModifyResponse:
66 - logger.info(f"Marking agent {agent_id} as critical")
67 - return mark_agent_criticality(agent_id, True)
68 -
69 -
70 -@agents_router.post("/{agent_id}/noncritical", response_model=AgentModifyResponse, description="Mark agent as not critical")
71 -async def mark_agent_as_not_critical(agent_id: str) -> AgentModifyResponse:
72 - logger.info(f"Marking agent {agent_id} as not critical")
73 - return mark_agent_criticality(agent_id, False)
74 -
75 -
76 -@agents_router.get("/{agent_id}/vulnerabilities", response_model=WazuhAgentVulnerabilitiesResponse, description="Get agent vulnerabilities")
77 -async def get_agent_vulnerabilities(agent_id: str) -> WazuhAgentVulnerabilitiesResponse:
78 - logger.info(f"Fetching agent {agent_id} vulnerabilities")
79 - return collect_agent_vulnerabilities(agent_id)
80 -
81 -
82 -@agents_router.get("/wazuh/outdated", response_model=OutdatedWazuhAgentsResponse, description="Get all outdated Wazuh agents")
83 -async def get_outdated_wazuh_agents() -> OutdatedWazuhAgentsResponse:
84 - logger.info("Fetching all outdated Wazuh agents")
85 - return get_outdated_agents_wazuh()
86 -
87 -
88 -@agents_router.get(
89 - "/velociraptor/outdated",
90 - response_model=OutdatedVelociraptorAgentsResponse,
91 - description="Get all outdated Velociraptor agents",
92 -)
93 -async def get_outdated_velociraptor_agents() -> OutdatedVelociraptorAgentsResponse:
94 - logger.info("Fetching all outdated Velociraptor agents")
95 - return get_outdated_agents_velociraptor()
96 -
97 -
98 -@agents_router.delete("/{agent_id}/delete", response_model=AgentModifyResponse, description="Delete agent")
99 -async def delete_agent(agent_id: str) -> AgentModifyResponse:
100 - logger.info(f"Deleting agent {agent_id}")
101 - # delete_agent_db(agent_id)
102 - # delete_agent_wazuh(agent_id)
103 - client_id = session.query(Agents).filter(Agents.agent_id == agent_id).first().velociraptor_id
104 - delete_agent_velociraptor(client_id)
105 - return {"success": True, "message": f"Agent {agent_id} deleted from database and Wazuh"}
106 -
107 -
108 -@agents_router.put(
109 - "/{agent_id}/update-customer-code",
110 - response_model=AgentUpdateCustomerCodeResponse,
111 - description="Update agent customer code",
112 -)
113 -async def update_agent_customer_code(agent_id: str, body: AgentUpdateCustomerCodeBody) -> AgentUpdateCustomerCodeResponse:
114 - logger.info(f"Updating agent {agent_id} customer code to {body.customer_code}")
115 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
116 - if not agent:
117 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
118 - agent.customer_code = body.customer_code
119 - session.commit()
120 - return {"success": True, "message": f"Agent {agent_id} customer code updated to {body.customer_code}"}
backend/app/agents/schema/agents.py deleted
-50
@@ -1,50 +0,0 @@
1 -from typing import List
2 -
3 -from pydantic import BaseModel
4 -from pydantic import Field
5 -
6 -from app.agents.velociraptor.schema.agents import VelociraptorAgent
7 -from app.agents.wazuh.schema.agents import WazuhAgent
8 -from app.db.universal_models import Agents
9 -
10 -
11 -class AgentsResponse(BaseModel):
12 - agents: List[Agents]
13 - success: bool
14 - message: str
15 -
16 -
17 -class SyncedAgent(WazuhAgent, VelociraptorAgent):
18 - pass
19 -
20 -
21 -class SyncedAgentsResponse(BaseModel):
22 - agents_added: List[SyncedAgent]
23 - success: bool
24 - message: str
25 -
26 -
27 -class AgentModifyResponse(BaseModel):
28 - success: bool
29 - message: str
30 -
31 -
32 -class OutdatedWazuhAgentsResponse(BaseModel):
33 - outdated_wazuh_agents: List[Agents]
34 - success: bool
35 - message: str
36 -
37 -
38 -class OutdatedVelociraptorAgentsResponse(BaseModel):
39 - outdated_velociraptor_agents: List[Agents]
40 - success: bool
41 - message: str
42 -
43 -
44 -class AgentUpdateCustomerCodeBody(BaseModel):
45 - customer_code: str = Field(None, description="Customer code to be updated")
46 -
47 -
48 -class AgentUpdateCustomerCodeResponse(BaseModel):
49 - success: bool
50 - message: str
backend/app/agents/services/modify.py deleted
-34
@@ -1,34 +0,0 @@
1 -from fastapi import HTTPException
2 -
3 -import app.agents.wazuh.services.agents as wazuh_services
4 -from app.db.db_session import session
5 -from app.db.universal_models import Agents
6 -
7 -
8 -def mark_agent_criticality(agent_id: str, critical: bool):
9 - """Mark agent as critical or not critical."""
10 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
11 - if not agent:
12 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
13 - agent.critical_asset = critical
14 - session.commit()
15 - return {"success": True, "message": f"Agent {agent_id} marked as critical: {critical}"}
16 -
17 -
18 -def delete_agent_db(agent_id: str):
19 - """Delete agent from database."""
20 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
21 - if not agent:
22 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
23 - session.delete(agent)
24 - session.commit()
25 - return {"success": True, "message": f"Agent {agent_id} deleted from database"}
26 -
27 -
28 -def delete_agent_wazuh(agent_id: str):
29 - """Delete agent from Wazuh service."""
30 - try:
31 - wazuh_services.delete_agent(agent_id)
32 - return {"success": True, "message": f"Agent {agent_id} deleted from Wazuh"}
33 - except Exception as e:
34 - raise HTTPException(status_code=500, detail=f"Failed to delete agent {agent_id} from Wazuh: {e}")
backend/app/agents/services/status.py deleted
-61
@@ -1,61 +0,0 @@
1 -from typing import List
2 -
3 -from loguru import logger
4 -
5 -from app.agents.schema.agents import OutdatedVelociraptorAgentsResponse
6 -from app.agents.schema.agents import OutdatedWazuhAgentsResponse
7 -from app.connectors.velociraptor.utils.universal import UniversalService
8 -from app.db.db_session import session
9 -from app.db.universal_models import Agents
10 -
11 -
12 -def get_agent(agent_id: str) -> List[Agents]:
13 - """
14 - Retrieves a specific agent from the database using its ID.
15 -
16 - Args:
17 - agent_id (str): The ID of the agent to retrieve.
18 -
19 - Returns:
20 - AgentMetadata: The agent object if found, otherwise None.
21 - """
22 - return session.query(Agents).filter(Agents.agent_id == agent_id).first()
23 -
24 -
25 -def get_outdated_agents_wazuh() -> OutdatedWazuhAgentsResponse:
26 - """
27 - Retrieves all agents with outdated Wazuh agent versions from the database.
28 -
29 - Returns:
30 - List[dict]: A list of dictionaries where each dictionary represents the serialized data of an outdated agent.
31 - """
32 - wazuh_manager = get_agent("000")
33 - if wazuh_manager is None:
34 - logger.error("Wazuh Manager with agent_id '000' not found.")
35 - return {"message": "Wazuh Manager with agent_id '000' not found.", "success": False}
36 -
37 - outdated_wazuh_agents = (
38 - session.query(Agents).filter(Agents.agent_id != "000", Agents.wazuh_agent_version != wazuh_manager.wazuh_agent_version).all()
39 - )
40 - return {"message": "Outdated Wazuh agents fetched successfully.", "success": True, "outdated_wazuh_agents": outdated_wazuh_agents}
41 -
42 -
43 -def get_outdated_agents_velociraptor() -> OutdatedVelociraptorAgentsResponse:
44 - """
45 - Retrieves all agents with outdated Velociraptor client versions from the database.
46 -
47 - Returns:
48 - List[dict]: A list of dictionaries where each dictionary represents the serialized data of an outdated agent.
49 - """
50 - outdated_velociraptor_agents = []
51 - vql_server_version = "select * from config"
52 - server_version = UniversalService()._get_server_version(vql_server_version)
53 - agents = session.query(Agents).all()
54 - for agent in agents:
55 - if agent.velociraptor_agent_version != server_version:
56 - outdated_velociraptor_agents.append(agent)
57 - return {
58 - "message": "Outdated Velociraptor agents fetched successfully.",
59 - "success": True,
60 - "outdated_velociraptor_agents": outdated_velociraptor_agents,
61 - }
backend/app/agents/services/sync.py deleted
-78
@@ -1,78 +0,0 @@
1 -from typing import List
2 -
3 -from loguru import logger
4 -
5 -import app.agents.velociraptor.services.agents as velociraptor_services
6 -import app.agents.wazuh.services.agents as wazuh_services
7 -from app.agents.schema.agents import SyncedAgent
8 -from app.agents.schema.agents import SyncedAgentsResponse
9 -from app.agents.velociraptor.schema.agents import VelociraptorAgent
10 -from app.agents.wazuh.schema.agents import WazuhAgent
11 -from app.agents.wazuh.schema.agents import WazuhAgentsList
12 -from app.db.db_session import session
13 -from app.db.universal_models import Agents
14 -
15 -
16 -def fetch_wazuh_agents() -> WazuhAgentsList:
17 - """Fetch agents from Wazuh service."""
18 - collected_wazuh_agents = wazuh_services.collect_wazuh_agents()
19 - return WazuhAgentsList(
20 - agents=collected_wazuh_agents.agents,
21 - success=collected_wazuh_agents.success,
22 - message=collected_wazuh_agents.message,
23 - )
24 -
25 -
26 -def fetch_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
27 - """Fetch agent details from Velociraptor service."""
28 - return velociraptor_services.collect_velociraptor_agent(agent_name)
29 -
30 -
31 -def add_agent_to_db(agent: WazuhAgent, client: VelociraptorAgent, customer_code: str):
32 - """Add new agent to database."""
33 - new_agent = Agents.create_from_model(agent, client, customer_code)
34 - session.add(new_agent)
35 - session.commit()
36 - logger.info(f"Agent {agent.agent_name} added to the database")
37 -
38 -
39 -def update_agent_in_db(existing_agent: Agents, agent: WazuhAgent, client: VelociraptorAgent, customer_code: str):
40 - """Update existing agent in database."""
41 - existing_agent.update_from_model(agent, client, customer_code)
42 - session.commit()
43 - logger.info(f"Agent {agent.agent_name} updated in the database")
44 -
45 -
46 -def extract_customer_code(customer_code: str):
47 - """Extract customer code from agent label."""
48 - parts = customer_code.split("_")
49 - return parts[1] if len(parts) > 1 else None
50 -
51 -
52 -def sync_agents() -> SyncedAgentsResponse:
53 - """Synchronize agents from Wazuh and Velociraptor services."""
54 - wazuh_agents_list = fetch_wazuh_agents()
55 - logger.info(f"Collected Wazuh Agents: {wazuh_agents_list}")
56 -
57 - agents_added_list: List[WazuhAgent] = []
58 -
59 - for wazuh_agent in wazuh_agents_list.agents:
60 - logger.info(f"Collecting Velociraptor Agent for {wazuh_agent.agent_name}")
61 -
62 - velociraptor_agent = fetch_velociraptor_agent(wazuh_agent.agent_name)
63 -
64 - customer_code = extract_customer_code(wazuh_agent.agent_label)
65 -
66 - existing_agent = session.query(Agents).filter(Agents.hostname == wazuh_agent.agent_name).first()
67 -
68 - if existing_agent:
69 - update_agent_in_db(existing_agent, wazuh_agent, velociraptor_agent, customer_code)
70 - else:
71 - add_agent_to_db(wazuh_agent, velociraptor_agent, customer_code)
72 -
73 - # Combine the wazuh agent and velociraptor agent into one object
74 - synced_agent = SyncedAgent(**wazuh_agent.dict(), **velociraptor_agent.dict())
75 - agents_added_list.append(synced_agent)
76 -
77 - logger.info(f"Agents Added List: {agents_added_list}")
78 - return SyncedAgentsResponse(success=True, message="Agents synced successfully", agents_added=agents_added_list)
backend/app/agents/velociraptor/schema/agents.py deleted
-19
@@ -1,19 +0,0 @@
1 -from datetime import datetime
2 -from typing import Optional
3 -
4 -from pydantic import BaseModel
5 -from pydantic import Field
6 -
7 -
8 -class VelociraptorAgent(BaseModel):
9 - client_id: Optional[str] = Field("n/a", alias="velociraptor_id")
10 - client_last_seen: str = Field(..., alias="velociraptor_last_seen")
11 - client_version: str = Field(..., alias="velociraptor_agent_version")
12 -
13 - @property
14 - def client_last_seen_as_datetime(self):
15 - dt = datetime.strptime(self.client_last_seen, "%Y-%m-%dT%H:%M:%S%z")
16 - return dt.replace(tzinfo=None)
17 -
18 - class Config:
19 - allow_population_by_field_name = True
backend/app/agents/velociraptor/services/agents.py deleted
-67
@@ -1,67 +0,0 @@
1 -from datetime import datetime
2 -
3 -from loguru import logger
4 -
5 -from app.agents.schema.agents import AgentsResponse
6 -from app.agents.velociraptor.schema.agents import VelociraptorAgent
7 -from app.connectors.velociraptor.services.artifacts import ArtifactsService
8 -from app.connectors.velociraptor.utils.universal import UniversalService
9 -
10 -
11 -def collect_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
12 - """
13 - Retrieves the client ID, last_seen_at and client version based on the agent name from Velociraptor.
14 -
15 - Args:
16 - agent_name (str): The name of the agent.
17 -
18 - Returns:
19 - str: The client ID if found, None otherwise.
20 - str: The last seen at timestamp if found, Default timsetamp otherwise.
21 - """
22 - logger.info(f"Collecting agent {agent_name} from Velociraptor")
23 - try:
24 - client_id = UniversalService().get_client_id(agent_name)["results"][0]["client_id"]
25 - except (KeyError, IndexError, TypeError) as e:
26 - logger.error(f"Failed to get client ID for {agent_name}. Error: {e}")
27 - return VelociraptorAgent(client_id="Unknown", client_last_seen="Unknown", client_version="Unknown")
28 -
29 - try:
30 - vql_last_seen_at = f"select last_seen_at from clients(search='host:{agent_name}')"
31 - last_seen_at = UniversalService()._get_last_seen_timestamp(vql_last_seen_at)
32 - client_last_seen = datetime.fromtimestamp(
33 - int(last_seen_at) / 1000000,
34 - ).strftime(
35 - "%Y-%m-%dT%H:%M:%S+00:00",
36 - ) # Converting to string format
37 - except Exception as e:
38 - logger.error(f"Failed to get or convert last seen at for {agent_name}. Error: {e}")
39 - client_last_seen = "1970-01-01T00:00:00+00:00"
40 -
41 - try:
42 - vql_client_version = f"select * from clients(search='host:{agent_name}')"
43 - client_version = UniversalService()._get_client_version(vql_client_version)
44 - except Exception as e:
45 - logger.error(f"Failed to get client version for {agent_name}. Error: {e}")
46 - client_version = "Unknown"
47 -
48 - return VelociraptorAgent(client_id=client_id, client_last_seen=client_last_seen, client_version=client_version)
49 -
50 -
51 -def delete_agent_velociraptor(client_id: str) -> AgentsResponse:
52 - """
53 - Deletes an agent from Velociraptor.
54 -
55 - Args:
56 - client_id (str): The client ID of the agent to delete.
57 -
58 - Returns:
59 - AgentsResponse: The response object.
60 - """
61 - logger.info(f"Deleting agent {client_id} from Velociraptor")
62 - try:
63 - ArtifactsService().delete_client(client_id=client_id)
64 - return AgentsResponse(success=True, message="Agent deleted successfully")
65 - except Exception as e:
66 - logger.error(f"Failed to delete agent {client_id}. Error: {e}")
67 - return AgentsResponse(success=False, message="Failed to delete agent")
backend/app/agents/velociraptor/utils/universal.py deleted
-20
@@ -1,20 +0,0 @@
1 -from datetime import datetime
2 -
3 -from loguru import logger
4 -
5 -
6 -def parse_date(date_string: str) -> datetime:
7 - """
8 - Parses a date string into a datetime object.
9 -
10 - Args:
11 - date_string (str): The date string to parse.
12 -
13 - Returns:
14 - datetime: The parsed datetime object.
15 - """
16 - try:
17 - return datetime.strptime(date_string, "%Y-%m-%dT%H:%M:%S+00:00")
18 - except ValueError:
19 - logger.info(f"Invalid format for date: {date_string}. Using the epoch time as default.")
20 - return datetime.strptime("1970-01-01T00:00:00+00:00", "%Y-%m-%dT%H:%M:%S+00:00")
backend/app/agents/wazuh/schema/agents.py deleted
-57
@@ -1,57 +0,0 @@
1 -from datetime import datetime
2 -from typing import List
3 -from typing import Optional
4 -
5 -from pydantic import BaseModel
6 -from pydantic import Field
7 -
8 -
9 -class WazuhAgent(BaseModel):
10 - agent_id: str = Field(..., alias="agent_id")
11 - agent_name: str = Field(..., alias="hostname")
12 - agent_ip: str = Field(..., alias="ip_address")
13 - agent_os: str = Field(..., alias="os")
14 - agent_label: str = Field(..., alias="label")
15 - agent_last_seen: str = Field(..., alias="wazuh_last_seen")
16 - wazuh_agent_version: str = Field(..., alias="wazuh_agent_version")
17 -
18 - @property
19 - def agent_last_seen_as_datetime(self):
20 - dt = datetime.strptime(self.agent_last_seen, "%Y-%m-%dT%H:%M:%S%z")
21 - return dt.replace(tzinfo=None)
22 -
23 - class Config:
24 - allow_population_by_field_name = True
25 -
26 -
27 -class WazuhAgentsList(BaseModel):
28 - agents: List[WazuhAgent]
29 - success: bool
30 - message: str
31 -
32 - class Config:
33 - allow_population_by_field_name = True
34 -
35 -
36 -class WazuhAgentVulnerabilities(BaseModel):
37 - severity: Optional[str]
38 - updated: Optional[str]
39 - version: Optional[str]
40 - type: Optional[str]
41 - name: Optional[str]
42 - external_references: Optional[List[str]]
43 - condition: Optional[str]
44 - detection_time: Optional[str]
45 - cvss3_score: Optional[float]
46 - published: Optional[str]
47 - architecture: Optional[str]
48 - cve: Optional[str]
49 - status: Optional[str]
50 - title: Optional[str]
51 - cvss2_score: Optional[float]
52 -
53 -
54 -class WazuhAgentVulnerabilitiesResponse(BaseModel):
55 - vulnerabilities: Optional[List[WazuhAgentVulnerabilities]]
56 - success: bool
57 - message: str
backend/app/agents/wazuh/services/agents.py deleted
-51
@@ -1,51 +0,0 @@
1 -from loguru import logger
2 -
3 -from app.agents.schema.agents import AgentModifyResponse
4 -from app.agents.wazuh.schema.agents import WazuhAgent
5 -from app.agents.wazuh.schema.agents import WazuhAgentsList
6 -from app.connectors.wazuh_manager.utils.universal import send_delete_request
7 -from app.connectors.wazuh_manager.utils.universal import send_get_request
8 -
9 -
10 -def collect_wazuh_agents() -> WazuhAgentsList:
11 - logger.info("Collecting all agents from Wazuh Manager")
12 - agents_collected = send_get_request(endpoint="/agents", params={"limit": 1000})
13 - logger.info(f"Agents collected: {agents_collected}")
14 - if agents_collected["success"]:
15 - wazuh_agents_list = []
16 - for agent in agents_collected["data"]["data"]["affected_items"]:
17 - os_name = agent.get("os", {}).get("name", "Unknown")
18 - last_keep_alive = agent.get("lastKeepAlive", "Unknown")
19 - agent_group_list = agent.get("group", [])
20 - agent_group = agent_group_list[0] if agent_group_list else "Unknown"
21 -
22 - wazuh_agent = WazuhAgent(
23 - agent_id=agent["id"],
24 - agent_name=agent["name"],
25 - agent_ip=agent["ip"],
26 - agent_os=os_name,
27 - agent_label=agent_group,
28 - agent_last_seen=last_keep_alive,
29 - wazuh_agent_version=agent["version"] if "version" in agent else "n/a",
30 - )
31 - wazuh_agents_list.append(wazuh_agent)
32 -
33 - return WazuhAgentsList(agents=wazuh_agents_list, success=True, message="Agents collected successfully")
34 - else:
35 - return WazuhAgentsList(agents=[], success=False, message="Failed to collect agents")
36 -
37 -
38 -def delete_agent(agent_id: str) -> AgentModifyResponse:
39 - """Delete agent from Wazuh Manager."""
40 - logger.info(f"Deleting agent {agent_id} from Wazuh Manager")
41 - params = {
42 - "purge": True,
43 - "agents_list": [agent_id],
44 - "status": "all",
45 - "older_than": "0s",
46 - }
47 - agent_deleted = send_delete_request(endpoint="/agents", params=params)
48 - if agent_deleted["success"]:
49 - return AgentModifyResponse(success=True, message="Agent deleted successfully")
50 - else:
51 - return AgentModifyResponse(success=False, message="Failed to delete agent")
backend/app/agents/wazuh/services/vulnerabilities.py deleted
-25
@@ -1,25 +0,0 @@
1 -from typing import List
2 -
3 -from loguru import logger
4 -
5 -from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilities
6 -from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilitiesResponse
7 -from app.connectors.wazuh_manager.utils.universal import send_get_request
8 -
9 -
10 -def collect_agent_vulnerabilities(agent_id: str):
11 - """Collect agent vulnerabilities from Wazuh Manager."""
12 - logger.info(f"Collecting agent {agent_id} vulnerabilities from Wazuh Manager")
13 - agent_vulnerabilities = send_get_request(endpoint=f"/vulnerability/{agent_id}")
14 - if agent_vulnerabilities["success"]:
15 - processed_vulnerabilities = process_agent_vulnerabilities(agent_vulnerabilities["data"])
16 - return WazuhAgentVulnerabilitiesResponse(
17 - vulnerabilities=processed_vulnerabilities,
18 - success=True,
19 - message="Vulnerabilities collected successfully",
20 - )
21 -
22 -
23 -def process_agent_vulnerabilities(agent_vulnerabilities: dict) -> List[WazuhAgentVulnerabilities]:
24 - vulnerabilities = agent_vulnerabilities.get("data", {}).get("affected_items", [])
25 - return [WazuhAgentVulnerabilities(**vuln) for vuln in vulnerabilities]
backend/app/auth/models/users.py deleted
-77
@@ -1,77 +0,0 @@
1 -import datetime
2 -from enum import Enum
3 -from typing import Optional
4 -
5 -from pydantic import EmailStr
6 -from pydantic import validator
7 -from sqlmodel import Field
8 -from sqlmodel import Relationship
9 -from sqlmodel import SQLModel
10 -
11 -
12 -class Role(SQLModel, table=True):
13 - id: Optional[int] = Field(primary_key=True)
14 - name: str = Field(max_length=256)
15 - description: str = Field(max_length=256)
16 -
17 - user: Optional["User"] = Relationship(back_populates="role")
18 -
19 -
20 -class User(SQLModel, table=True):
21 - id: Optional[int] = Field(primary_key=True)
22 - username: str = Field(index=True)
23 - password: str = Field(max_length=256, min_length=6)
24 - email: EmailStr
25 - created_at: datetime.datetime = datetime.datetime.now()
26 - role_id: Optional[int] = Field(foreign_key="role.id")
27 -
28 - smtp: "SMTP" = Relationship(back_populates="user")
29 - role: Optional["Role"] = Relationship(back_populates="user")
30 -
31 -
32 -# Enum class for role_id 1,2
33 -class RoleEnum(int, Enum):
34 - admin = 1
35 - analyst = 2
36 -
37 -
38 -class UserInput(SQLModel):
39 - username: str
40 - password: str = Field(
41 - max_length=256,
42 - min_length=8,
43 - regex="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$",
44 - description="Password must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, and one number",
45 - )
46 - email: EmailStr
47 - role_id: RoleEnum = Field(RoleEnum.analyst, description="Role ID 1: admin, 2: analyst", foreign_key="role.id")
48 -
49 -
50 -class UserLogin(SQLModel):
51 - username: str
52 - password: str
53 -
54 -
55 -class SMTP(SQLModel, table=True):
56 - id: Optional[int] = Field(primary_key=True)
57 - email: EmailStr
58 - smtp_password: str = Field(max_length=256)
59 - smtp_server: str = Field(max_length=256)
60 - smtp_port: int
61 - user_id: int = Field(foreign_key="user.id")
62 -
63 - user: "User" = Relationship(back_populates="smtp")
64 -
65 -
66 -class SMTPInput(SQLModel):
67 - email: EmailStr
68 - smtp_password: str = Field(max_length=256)
69 - smtp_password2: str = Field(max_length=256)
70 - smtp_server: str = Field(max_length=256)
71 - smtp_port: int
72 -
73 - @validator("smtp_password2")
74 - def password_match(cls, v, values, **kwargs):
75 - if "smtp_password" in values and v != values["smtp_password"]:
76 - raise ValueError("passwords don't match")
77 - return v
backend/app/auth/routes/auth.py deleted
-73
@@ -1,73 +0,0 @@
1 -from datetime import timedelta
2 -
3 -from fastapi import APIRouter
4 -from fastapi import Depends
5 -from fastapi import HTTPException
6 -from fastapi import status
7 -from fastapi.security import OAuth2PasswordRequestForm
8 -
9 -from app.auth.models.users import User
10 -from app.auth.models.users import UserInput
11 -from app.auth.models.users import UserLogin
12 -from app.auth.schema.auth import Token
13 -from app.auth.schema.auth import UserLoginResponse
14 -from app.auth.schema.auth import UserResponse
15 -from app.auth.services.universal import find_user
16 -from app.auth.services.universal import select_all_users
17 -from app.auth.utils import AuthHandler
18 -from app.db.db_session import session
19 -
20 -ACCESS_TOKEN_EXPIRE_MINUTES = 1440
21 -
22 -user_router = APIRouter()
23 -auth_handler = AuthHandler()
24 -
25 -
26 -@user_router.post("/token", response_model=Token)
27 -async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
28 - user = auth_handler.authenticate_user(form_data.username, form_data.password)
29 - if not user:
30 - raise HTTPException(
31 - status_code=status.HTTP_401_UNAUTHORIZED,
32 - detail="Incorrect username or password",
33 - headers={"WWW-Authenticate": "Bearer"},
34 - )
35 - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
36 - access_token = auth_handler.encode_token(user.username, access_token_expires)
37 - return {"access_token": access_token, "token_type": "bearer"}
38 -
39 -
40 -@user_router.get("/refresh", response_model=Token)
41 -async def refresh_token(current_user: User = Depends(auth_handler.get_current_user)):
42 - access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
43 - access_token = auth_handler.encode_token(current_user.username, access_token_expires)
44 - return {"access_token": access_token, "token_type": "bearer"}
45 -
46 -
47 -@user_router.post("/register", response_model=UserResponse, status_code=201, description="Register new user")
48 -def register(user: UserInput):
49 - users = select_all_users()
50 - if any(x.username == user.username for x in users):
51 - raise HTTPException(status_code=400, detail="Username is taken")
52 - hashed_pwd = auth_handler.get_password_hash(user.password)
53 - u = User(username=user.username, password=hashed_pwd, email=user.email, role_id=user.role_id)
54 - session.add(u)
55 - session.commit()
56 - return {"message": "User created successfully", "success": True}
57 -
58 -
59 -@user_router.post("/login", response_model=UserLoginResponse, description="Login user", deprecated=True)
60 -def login(user: UserLogin):
61 - user_found = find_user(user.username)
62 - if not user_found:
63 - raise HTTPException(status_code=401, detail="Invalid username and/or password")
64 - verified = auth_handler.verify_password(user.password, user_found.password)
65 - if not verified:
66 - raise HTTPException(status_code=401, detail="Invalid username and/or password")
67 - token = auth_handler.encode_token(user_found.username)
68 - return {"token": token, "success": True, "message": "Login successful"}
69 -
70 -
71 -# @user_router.get("/users/me", description="Get current user")
72 -# def get_current_user(user: User = Depends(auth_handler.get_current_user)):
73 -# return user
backend/app/auth/schema/auth.py deleted
-21
@@ -1,21 +0,0 @@
1 -from pydantic import BaseModel
2 -
3 -
4 -class UserResponse(BaseModel):
5 - message: str
6 - success: bool
7 -
8 -
9 -class UserLoginResponse(BaseModel):
10 - token: str
11 - message: str
12 - success: bool
13 -
14 -
15 -class Token(BaseModel):
16 - access_token: str
17 - token_type: str
18 -
19 -
20 -class TokenData(BaseModel):
21 - username: str | None = None
backend/app/auth/services/universal.py deleted
-29
@@ -1,29 +0,0 @@
1 -from sqlmodel import Session
2 -from sqlmodel import select
3 -
4 -from app.auth.models.users import Role
5 -from app.auth.models.users import User
6 -from app.db.db_session import engine
7 -
8 -
9 -def select_all_users():
10 - with Session(engine) as session:
11 - statement = select(User)
12 - res = session.exec(statement).all()
13 - return res
14 -
15 -
16 -def find_user(name):
17 - with Session(engine) as session:
18 - statement = select(User).where(User.username == name)
19 - return session.exec(statement).first()
20 -
21 -
22 -def get_role(name):
23 - with Session(engine) as session:
24 - statement = select(User).where(User.username == name)
25 - res = session.exec(statement).first()
26 - # Get the role name
27 - statement = select(Role).where(Role.id == res.role_id)
28 - role = session.exec(statement).first()
29 - return role.name
backend/app/auth/utils.py deleted
-122
@@ -1,122 +0,0 @@
1 -from datetime import datetime
2 -from datetime import timedelta
3 -
4 -import jwt
5 -from fastapi import Depends
6 -from fastapi import HTTPException
7 -from fastapi.security import OAuth2PasswordBearer
8 -from fastapi.security import SecurityScopes
9 -from passlib.context import CryptContext
10 -
11 -from app.auth.services.universal import find_user
12 -from app.auth.services.universal import get_role
13 -
14 -
15 -class AuthHandler:
16 - security = OAuth2PasswordBearer(
17 - tokenUrl="auth/token",
18 - scopes={"admin": "Admin users", "analyst": "SOC Analysts"},
19 - )
20 - pwd_context = CryptContext(schemes=["bcrypt"])
21 - secret = "bL4unrkoxtFs1MT6A7Ns2yMLkduyuqrkTxDV9CjlbNc="
22 -
23 - def get_password_hash(self, password):
24 - return self.pwd_context.hash(password)
25 -
26 - def verify_password(self, plain_password, hashed_password):
27 - return self.pwd_context.verify(plain_password, hashed_password)
28 -
29 - def authenticate_user(self, username: str, password: str):
30 - user = find_user(username)
31 - if not user or not self.verify_password(password, user.password):
32 - return False
33 - return user
34 -
35 - def encode_token(self, username: str, access_token_expires: timedelta = timedelta(minutes=60)):
36 - payload = {
37 - "exp": datetime.utcnow() + access_token_expires,
38 - "iat": datetime.utcnow(),
39 - "sub": username,
40 - "scopes": [get_role(username)],
41 - }
42 - return jwt.encode(payload, self.secret, algorithm="HS256")
43 -
44 - def decode_token(self, token):
45 - try:
46 - payload = jwt.decode(token, self.secret, algorithms=["HS256"])
47 - return payload["sub"], payload.get("scopes", [])
48 - except jwt.ExpiredSignatureError:
49 - raise HTTPException(status_code=401, detail="Expired signature")
50 - except jwt.InvalidTokenError:
51 - raise HTTPException(status_code=401, detail="Invalid token")
52 -
53 - def get_current_user(self, security_scopes: SecurityScopes, token: str = Depends(security)):
54 - if security_scopes.scopes:
55 - authenticate_value = f'Bearer scope="{security_scopes.scope_str}"'
56 - else:
57 - authenticate_value = "Bearer"
58 -
59 - credentials_exception = HTTPException(
60 - status_code=401,
61 - detail="Could not validate credentials",
62 - headers={"WWW-Authenticate": authenticate_value},
63 - )
64 -
65 - try:
66 - username, token_scopes = self.decode_token(token)
67 - except Exception as e:
68 - raise HTTPException(
69 - status_code=401,
70 - detail=f"Could not decode token: {e}",
71 - headers={"WWW-Authenticate": authenticate_value},
72 - )
73 -
74 - if username is None:
75 - raise HTTPException(
76 - status_code=401,
77 - detail="Username not found in token",
78 - headers={"WWW-Authenticate": authenticate_value},
79 - )
80 - user = find_user(username)
81 - if user is None:
82 - raise HTTPException(
83 - status_code=401,
84 - detail="User not found",
85 - headers={"WWW-Authenticate": authenticate_value},
86 - )
87 -
88 - for scope in security_scopes.scopes:
89 - if scope not in token_scopes:
90 - raise HTTPException(
91 - status_code=401,
92 - detail="Not enough permissions",
93 - headers={"WWW-Authenticate": authenticate_value},
94 - )
95 -
96 - return user
97 -
98 - def return_username_for_logging(self, token: str = Depends(security)):
99 - username, token_scopes = self.decode_token(token)
100 - return username
101 -
102 - def require_any_scope(self, *required_scopes: str):
103 - async def _require_any_scope(token: str = Depends(self.security)):
104 - if not token:
105 - raise HTTPException(
106 - status_code=401,
107 - detail="Not authenticated",
108 - headers={"WWW-Authenticate": "Bearer"},
109 - )
110 -
111 - username, token_scopes = self.decode_token(token)
112 -
113 - if not any(scope in token_scopes for scope in required_scopes):
114 - raise HTTPException(
115 - status_code=401,
116 - detail="Not enough permissions, you don't have any of the required scopes.",
117 - headers={"WWW-Authenticate": "Bearer"},
118 - )
119 -
120 - return username
121 -
122 - return _require_any_scope
backend/app/auth/utils_backup.py deleted
-53
@@ -1,53 +0,0 @@
1 -#### ! COMMENTING OUT FOR PRECOMMIT TO PASS ####
2 -# import datetime
3 -# from typing import Optional
4 -#### ! COMMENTING OUT FOR PRECOMMIT TO PASS ####
5 -# import jwt
6 -# from fastapi import HTTPException
7 -# from fastapi import Security
8 -# from fastapi.security import HTTPAuthorizationCredentials
9 -# from fastapi.security import HTTPBearer
10 -# from fastapi.security import OAuth2PasswordBearer
11 -# from passlib.context import CryptContext
12 -# from starlette import status
13 -
14 -# from app.auth.models.users import User
15 -# from app.auth.services.universal import find_user
16 -
17 -
18 -# class AuthHandler:
19 -# security = HTTPBearer()
20 -# pwd_context = CryptContext(schemes=["bcrypt"])
21 -# secret = "supersecret"
22 -
23 -# def get_password_hash(self, password):
24 -# return self.pwd_context.hash(password)
25 -
26 -# def verify_password(self, pwd, hashed_pwd):
27 -# return self.pwd_context.verify(pwd, hashed_pwd)
28 -
29 -# def encode_token(self, user_id):
30 -# payload = {"exp": datetime.datetime.utcnow() + datetime.timedelta(hours=8), "iat": datetime.datetime.utcnow(), "sub": user_id}
31 -# return jwt.encode(payload, self.secret, algorithm="HS256")
32 -
33 -# def decode_token(self, token):
34 -# try:
35 -# payload = jwt.decode(token, self.secret, algorithms=["HS256"])
36 -# return payload["sub"]
37 -# except jwt.ExpiredSignatureError:
38 -# raise HTTPException(status_code=401, detail="Expired signature")
39 -# except jwt.InvalidTokenError:
40 -# raise HTTPException(status_code=401, detail="Invalid token")
41 -
42 -# def auth_wrapper(self, auth: HTTPAuthorizationCredentials = Security(security)):
43 -# return self.decode_token(auth.credentials)
44 -
45 -# def get_current_user(self, auth: HTTPAuthorizationCredentials = Security(security)):
46 -# credentials_exception = HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Could not validate credentials")
47 -# username = self.decode_token(auth.credentials)
48 -# if username is None:
49 -# raise credentials_exception
50 -# user = find_user(username)
51 -# if user is None:
52 -# raise credentials_exception
53 -# return user
backend/app/connectors/cortex/routes/analyzers.py deleted
-44
@@ -1,44 +0,0 @@
1 -from typing import List
2 -
3 -from fastapi import APIRouter
4 -from fastapi import Depends
5 -from fastapi import HTTPException
6 -from loguru import logger
7 -
8 -from app.connectors.cortex.schema.analyzers import AnalyzersResponse
9 -from app.connectors.cortex.schema.analyzers import RunAnalyzerBody
10 -from app.connectors.cortex.schema.analyzers import RunAnalyzerResponse
11 -from app.connectors.cortex.services.analyzers import get_analyzers
12 -from app.connectors.cortex.services.analyzers import run_analyzer
13 -
14 -# App specific imports
15 -
16 -
17 -cortex_analyzer_router = APIRouter()
18 -
19 -
20 -def get_available_analyzers() -> List[str]:
21 - return get_analyzers().analyzers
22 -
23 -
24 -def verify_analyzer_exists(run_analyzer_body: RunAnalyzerBody) -> RunAnalyzerBody:
25 - available_analyzers = get_available_analyzers()
26 - if run_analyzer_body.analyzer_name not in available_analyzers:
27 - raise HTTPException(status_code=400, detail=f"Analyzer {run_analyzer_body.analyzer_name} does not exist.")
28 - return run_analyzer_body
29 -
30 -
31 -@cortex_analyzer_router.get("", response_model=AnalyzersResponse, description="Get all analyzers")
32 -async def get_all_analyzers() -> AnalyzersResponse:
33 - logger.info("Fetching all analyzers")
34 - return get_analyzers()
35 -
36 -
37 -@cortex_analyzer_router.post("/run", response_model=RunAnalyzerResponse, description="Run an analyzer")
38 -async def run_analyzer_route(run_analyzer_body: RunAnalyzerBody = Depends(verify_analyzer_exists)) -> RunAnalyzerResponse:
39 - is_valid, data_type = RunAnalyzerBody.is_valid_datatype(run_analyzer_body.analyzer_data)
40 - if not is_valid:
41 - raise HTTPException(status_code=400, detail=f"Invalid data type: {data_type}")
42 -
43 - logger.info(f"Running analyzer {run_analyzer_body.analyzer_name} with data {run_analyzer_body.analyzer_data} of type {data_type}")
44 - return run_analyzer(run_analyzer_body, data_type)
backend/app/connectors/cortex/schema/analyzers.py deleted
-76
@@ -1,76 +0,0 @@
1 -import ipaddress
2 -import re
3 -from typing import Any
4 -from typing import Dict
5 -from typing import List
6 -from typing import Optional
7 -from typing import Tuple
8 -
9 -from pydantic import BaseModel
10 -from pydantic import Field
11 -from pydantic import validator
12 -
13 -HASH_REGEX = re.compile(r"[a-fA-F\d]{32}|[a-fA-F\d]{64}") # Update this regex to match your specific hash format
14 -DOMAIN_REGEX = re.compile(
15 - r"^(?:[a-z0-9](?:[a-z0-9\-]{0,61}[a-z0-9])?\.)+[a-z]{2,6}$",
16 -) # Update this regex to match your specific domain format
17 -
18 -
19 -class AnalyzersResponse(BaseModel):
20 - analyzers: List[str]
21 - message: str
22 - success: bool
23 -
24 -
25 -class RunAnalyzerBody(BaseModel):
26 - analyzer_name: str = Field(..., description="Name of the analyzer to be run.")
27 - analyzer_data: str = Field(..., description="The Indicator of Compromise (IoC) to be analyzed.")
28 - data_type: Optional[str] = Field(default=None, description="Data type determined after validation")
29 -
30 - @validator("analyzer_data", pre=True, always=True)
31 - def validate_and_set_data_type(cls, value: str, values: dict) -> str:
32 - is_valid, data_type = cls.is_valid_datatype(value)
33 - if not is_valid:
34 - raise ValueError(f"Invalid data type: {data_type}")
35 - values["data_type"] = data_type
36 - return value
37 -
38 - @classmethod
39 - def is_valid_datatype(cls, value: str) -> Tuple[bool, str]:
40 - if cls._is_valid_ipv4(value):
41 - return True, "ip"
42 - elif cls._is_valid_hash(value):
43 - return True, "hash"
44 - elif cls._is_valid_domain(value):
45 - return True, "domain"
46 - else:
47 - return False, "Unknown"
48 -
49 - @staticmethod
50 - def _is_valid_ipv4(value: str) -> bool:
51 - try:
52 - ipaddress.IPv4Address(value)
53 - return True
54 - except ValueError:
55 - return False
56 -
57 - @staticmethod
58 - def _is_valid_hash(value: str) -> bool:
59 - return bool(HASH_REGEX.match(value))
60 -
61 - @staticmethod
62 - def _is_valid_domain(value: str) -> bool:
63 - return bool(DOMAIN_REGEX.match(value))
64 -
65 -
66 -class RunAnalyzerResponse(BaseModel):
67 - report: Dict[str, Any]
68 - message: str
69 - success: bool
70 -
71 -
72 -class AnalyzerJobData(BaseModel):
73 - data: str = Field(..., description="The Indicator of Compromise (IoC) to be analyzed.")
74 - dataType: str = Field(..., description="The type of the IoC (e.g., 'IP', 'hash', 'domain').")
75 - tlp: int = Field(1, description="Traffic Light Protocol (TLP) level.")
76 - message: str = Field("custom message sent to analyzer", description="Custom message.")
backend/app/connectors/cortex/services/analyzers.py deleted
-71
@@ -1,71 +0,0 @@
1 -# analyzers.py
2 -
3 -from typing import Dict
4 -from typing import List
5 -from typing import Union
6 -
7 -from cortex4py.api import Api
8 -from fastapi import HTTPException
9 -from loguru import logger
10 -
11 -from app.connectors.cortex.schema.analyzers import AnalyzerJobData
12 -from app.connectors.cortex.schema.analyzers import AnalyzersResponse
13 -from app.connectors.cortex.schema.analyzers import RunAnalyzerBody
14 -from app.connectors.cortex.schema.analyzers import RunAnalyzerResponse
15 -from app.connectors.cortex.utils.universal import (
16 - create_cortex_client, # Importing create_cortex_client
17 -)
18 -from app.connectors.cortex.utils.universal import (
19 - run_and_wait_for_analyzer, # Importing from universal.py
20 -)
21 -
22 -
23 -def fetch_analyzers(api: Api) -> List[Dict]:
24 - return api.analyzers.find_all({}, range="all")
25 -
26 -
27 -def extract_analyzer_names(analyzers: List[Dict]) -> List[str]:
28 - try:
29 - return [analyzer.name for analyzer in analyzers]
30 - except Exception as e:
31 - logger.error(f"Error processing analyzers: {e}")
32 - raise HTTPException(status_code=500, detail=f"Error processing analyzers: {e}")
33 -
34 -
35 -def init_cortex_client() -> Union[Api, None]:
36 - return create_cortex_client("Cortex")
37 -
38 -
39 -def handle_api_initialization(api: Union[Api, None]) -> Api:
40 - if api is None:
41 - logger.error("API initialization failed")
42 - raise HTTPException(status_code=500, detail="API initialization failed")
43 - return api
44 -
45 -
46 -def get_analyzers() -> AnalyzersResponse:
47 - api = init_cortex_client()
48 - handle_api_initialization(api)
49 -
50 - analyzers = fetch_analyzers(api)
51 - analyzer_names = extract_analyzer_names(analyzers)
52 -
53 - return AnalyzersResponse(success=True, message="Successfully fetched analyzers", analyzers=analyzer_names)
54 -
55 -
56 -def run_analyzer(run_analyzer_body: RunAnalyzerBody, data_type: str) -> RunAnalyzerResponse:
57 - api = init_cortex_client()
58 - handle_api_initialization(api)
59 -
60 - analyzer_name = run_analyzer_body.analyzer_name
61 - analyzer_data = run_analyzer_body.analyzer_data
62 - logger.info(f"Running analyzer {analyzer_name} with data {analyzer_data} of type {data_type}")
63 - job_data = AnalyzerJobData(data=analyzer_data, dataType=data_type)
64 -
65 - result = run_and_wait_for_analyzer(analyzer_name=analyzer_name, job_data=job_data)
66 -
67 - if result is None:
68 - logger.error(f"Failed to run analyzer {analyzer_name}")
69 - raise HTTPException(status_code=500, detail=f"Failed to run analyzer {analyzer_name}")
70 -
71 - return RunAnalyzerResponse(success=True, message="Successfully ran analyzer", report=result)
backend/app/connectors/cortex/utils/universal.py deleted
-112
@@ -1,112 +0,0 @@
1 -import time
2 -import traceback
3 -from typing import Any
4 -from typing import Dict
5 -
6 -from cortex4py.api import Api
7 -from loguru import logger
8 -
9 -from app.connectors.cortex.schema.analyzers import AnalyzerJobData
10 -from app.connectors.utils import get_connector_info_from_db
11 -
12 -
13 -def verify_cortex_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
14 - """
15 - Verifies the connection to Cortex service.
16 -
17 - Returns:
18 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
19 - """
20 - logger.info(f"Verifying the Cortex connection to {attributes['connector_url']}")
21 -
22 - try:
23 - api = Api(attributes["connector_url"], attributes["connector_api_key"], verify_cert=False)
24 - # Get Cortex Status
25 - status = api.status
26 - if status:
27 - logger.debug("Cortex connection successful")
28 - return {"connectionSuccessful": True, "message": "Cortex connection successful"}
29 - else:
30 - logger.error(f"Connection to {attributes['connector_url']} failed with error.")
31 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error."}
32 - except Exception as e:
33 - logger.error(f"Connection to {attributes['connector_url']} failed with error: {e}")
34 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error: {e}"}
35 -
36 -
37 -def verify_cortex_connection(connector_name: str) -> str:
38 - """
39 - Returns the authentication token for the Cortex service.
40 -
41 - Returns:
42 - str: Authentication token for the Cortex service.
43 - """
44 - attributes = get_connector_info_from_db(connector_name)
45 - if attributes is None:
46 - logger.error("No Cortex connector found in the database")
47 - return None
48 - return verify_cortex_credentials(attributes)
49 -
50 -
51 -def create_cortex_client(connector_name: str) -> Api:
52 - """
53 - Returns an Cortex client for the Wazuh Indexer service.
54 -
55 - Returns:
56 - Cortex: Cortex client for the Cortex service.
57 - """
58 - attributes = get_connector_info_from_db(connector_name)
59 - if attributes is None:
60 - logger.error("No Wazuh Indexer connector found in the database")
61 - return None
62 - return Api(attributes["connector_url"], attributes["connector_api_key"], verify_cert=False)
63 -
64 -
65 -def run_and_wait_for_analyzer(analyzer_name: str, job_data: AnalyzerJobData) -> Dict[str, Any]:
66 - api = create_cortex_client("Cortex") # Create Api object
67 - if api is None:
68 - return {"success": False, "message": "API initialization failed"}
69 - try:
70 - job = api.analyzers.run_by_name(analyzer_name, job_data.dict(), force=1)
71 - return monitor_analyzer_job(api, job)
72 - except Exception as e:
73 - logger.error(f"Error running analyzer {analyzer_name}: {e}")
74 - logger.debug(f"job_data dict: {job_data.dict()}")
75 - logger.debug(f"Exception details: {traceback.format_exc()}")
76 - logger.debug(f"Error running analyzer {analyzer_name}: {e}", exc_info=True)
77 - return {"success": False, "message": f"Error running analyzer {analyzer_name}: {e}"}
78 -
79 -
80 -def monitor_analyzer_job(api: Api, job: Any) -> Dict[str, Any]:
81 - r_json = job.json()
82 - job_id = r_json["id"]
83 - logger.info(f"Job ID is: {job_id}")
84 -
85 - job_state = r_json["status"]
86 - timer = 0
87 -
88 - while job_state != "Success":
89 - if timer == 60:
90 - logger.error("Job failed to complete after 5 minutes.")
91 - return {"success": False, "message": "Job timed out"}
92 -
93 - timer += 1
94 - logger.info(f"Timer is: {timer}")
95 -
96 - if job_state == "Failure":
97 - error_message = r_json["errorMessage"]
98 - logger.error(f"Cortex Failure: {error_message}")
99 - return {"success": False, "message": f"Analyzer failed: {error_message}"}
100 -
101 - time.sleep(5)
102 - followup_request = api.jobs.get_by_id(job_id)
103 - r_json = followup_request.json()
104 - job_state = r_json["status"]
105 -
106 - return retrieve_final_report(api, job_id)
107 -
108 -
109 -def retrieve_final_report(api: Api, job_id: str) -> Dict[str, Any]:
110 - report = api.jobs.get_report(job_id).report
111 - final_report = report["full"]
112 - return {"success": True, "message": "Analyzer ran successfully", "report": final_report}
backend/app/connectors/dfir_iris/routes/alerts.py deleted
-47
@@ -1,47 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import Depends
3 -from fastapi import HTTPException
4 -from loguru import logger
5 -
6 -from app.connectors.dfir_iris.schema.alerts import AlertResponse
7 -from app.connectors.dfir_iris.schema.alerts import AlertsResponse
8 -from app.connectors.dfir_iris.schema.alerts import BookmarkedAlertsResponse
9 -from app.connectors.dfir_iris.services.alerts import bookmark_alert
10 -from app.connectors.dfir_iris.services.alerts import get_alerts
11 -from app.connectors.dfir_iris.services.alerts import get_bookmarked_alerts
12 -from app.connectors.dfir_iris.utils.universal import check_alert_exists
13 -
14 -# App specific imports
15 -
16 -
17 -def verify_alert_exists(alert_id: str) -> str:
18 - if not check_alert_exists(alert_id):
19 - raise HTTPException(status_code=400, detail=f"Alert {alert_id} does not exist.")
20 - return alert_id
21 -
22 -
23 -dfir_iris_alerts_router = APIRouter()
24 -
25 -
26 -@dfir_iris_alerts_router.get("", response_model=AlertsResponse, description="Get all alerts")
27 -async def get_all_alerts() -> AlertsResponse:
28 - logger.info("Fetching all alerts")
29 - return get_alerts()
30 -
31 -
32 -@dfir_iris_alerts_router.get("/bookmark", response_model=BookmarkedAlertsResponse, description="Get all bookmarked alerts")
33 -async def get_all_bookmarked_alerts() -> BookmarkedAlertsResponse:
34 - logger.info("Fetching all bookmarked alerts")
35 - return get_bookmarked_alerts()
36 -
37 -
38 -@dfir_iris_alerts_router.post("/bookmark/{alert_id}", response_model=AlertResponse, description="Bookmark an alert")
39 -async def bookmark_alert_route(alert_id: str = Depends(verify_alert_exists)) -> AlertResponse:
40 - logger.info(f"Bookmarking alert {alert_id}")
41 - return bookmark_alert(alert_id, bookmarked=True)
42 -
43 -
44 -@dfir_iris_alerts_router.delete("/bookmark/{alert_id}", response_model=AlertResponse, description="Unbookmark an alert")
45 -async def unbookmark_alert_route(alert_id: str = Depends(verify_alert_exists)) -> AlertResponse:
46 - logger.info(f"Unbookmarking alert {alert_id}")
47 - return bookmark_alert(alert_id, bookmarked=False)
backend/app/connectors/dfir_iris/routes/assets.py deleted
-25
@@ -1,25 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import Depends
3 -from fastapi import HTTPException
4 -from loguru import logger
5 -
6 -from app.connectors.dfir_iris.schema.assets import AssetResponse
7 -from app.connectors.dfir_iris.services.assets import get_case_assets
8 -from app.connectors.dfir_iris.utils.universal import check_case_exists
9 -
10 -# App specific imports
11 -
12 -
13 -def verify_case_exists(case_id: int) -> int:
14 - if not check_case_exists(case_id):
15 - raise HTTPException(status_code=400, detail=f"Case {case_id} does not exist.")
16 - return case_id
17 -
18 -
19 -assets_router = APIRouter()
20 -
21 -
22 -@assets_router.get("/{case_id}", response_model=AssetResponse, description="Get all assets for a case")
23 -async def get_case_assets_route(case_id: int = Depends(verify_case_exists)) -> AssetResponse:
24 - logger.info(f"Fetching assets for case {case_id}")
25 - return get_case_assets(case_id)
backend/app/connectors/dfir_iris/routes/cases.py deleted
-56
@@ -1,56 +0,0 @@
1 -from datetime import timedelta
2 -
3 -from fastapi import APIRouter
4 -from fastapi import Depends
5 -from fastapi import HTTPException
6 -from loguru import logger
7 -
8 -from app.connectors.dfir_iris.schema.cases import CaseOlderThanBody
9 -from app.connectors.dfir_iris.schema.cases import CaseResponse
10 -from app.connectors.dfir_iris.schema.cases import CasesBreachedResponse
11 -from app.connectors.dfir_iris.schema.cases import SingleCaseBody
12 -from app.connectors.dfir_iris.schema.cases import SingleCaseResponse
13 -from app.connectors.dfir_iris.schema.cases import TimeUnit
14 -from app.connectors.dfir_iris.services.cases import get_all_cases
15 -from app.connectors.dfir_iris.services.cases import get_cases_older_than
16 -from app.connectors.dfir_iris.services.cases import get_single_case
17 -from app.connectors.dfir_iris.utils.universal import check_case_exists
18 -
19 -
20 -def verify_case_exists(case_id: int) -> int:
21 - if not check_case_exists(case_id):
22 - raise HTTPException(status_code=400, detail=f"Case {case_id} does not exist.")
23 - return case_id
24 -
25 -
26 -cases_router = APIRouter()
27 -
28 -
29 -def get_timedelta(older_than: int, time_unit: TimeUnit) -> CaseOlderThanBody:
30 - delta = None
31 - if time_unit == TimeUnit.HOURS:
32 - delta = timedelta(hours=older_than)
33 - elif time_unit == TimeUnit.DAYS:
34 - delta = timedelta(days=older_than)
35 - elif time_unit == TimeUnit.WEEKS:
36 - delta = timedelta(weeks=older_than)
37 - return CaseOlderThanBody(older_than=delta, time_unit=time_unit)
38 -
39 -
40 -@cases_router.get("", response_model=CaseResponse, description="Get all cases")
41 -async def get_cases_route() -> CaseResponse:
42 - logger.info("Fetching all cases")
43 - return get_all_cases()
44 -
45 -
46 -@cases_router.post("/older_than", response_model=CasesBreachedResponse, description="Get all cases older than a specified date")
47 -async def get_cases_older_than_route(case_older_than_body: CaseOlderThanBody = Depends(get_timedelta)) -> CaseResponse:
48 - logger.info(f"Fetching all cases older than {case_older_than_body.older_than} ({case_older_than_body.time_unit.value})")
49 - return get_cases_older_than(case_older_than_body)
50 -
51 -
52 -@cases_router.get("/{case_id}", response_model=SingleCaseResponse, description="Get a single case")
53 -async def get_single_case_route(case_id: int = Depends(verify_case_exists)) -> SingleCaseResponse:
54 - logger.info(f"Fetching case {case_id}")
55 - single_case_body = SingleCaseBody(case_id=case_id)
56 - return get_single_case(single_case_body.case_id)
backend/app/connectors/dfir_iris/routes/notes.py deleted
-35
@@ -1,35 +0,0 @@
1 -from typing import Optional
2 -
3 -from fastapi import APIRouter
4 -from fastapi import Depends
5 -from fastapi import HTTPException
6 -from loguru import logger
7 -
8 -from app.connectors.dfir_iris.schema.notes import NoteCreationBody
9 -from app.connectors.dfir_iris.schema.notes import NoteCreationResponse
10 -from app.connectors.dfir_iris.schema.notes import NotesResponse
11 -from app.connectors.dfir_iris.services.notes import create_case_note
12 -from app.connectors.dfir_iris.services.notes import get_case_notes
13 -from app.connectors.dfir_iris.utils.universal import check_case_exists
14 -
15 -
16 -def verify_case_exists(case_id: int) -> int:
17 - if not check_case_exists(case_id):
18 - raise HTTPException(status_code=400, detail=f"Case {case_id} does not exist.")
19 - return case_id
20 -
21 -
22 -notes_router = APIRouter()
23 -
24 -
25 -@notes_router.get("/{case_id}", response_model=NotesResponse, description="Get all notes for a case")
26 -async def get_case_notes_route(case_id: int = Depends(verify_case_exists), search_term: Optional[str] = "%") -> NotesResponse:
27 - logger.info(f"Fetching notes for case {case_id}")
28 - return get_case_notes(case_id, search_term)
29 -
30 -
31 -@notes_router.post("/{case_id}", response_model=NoteCreationResponse, description="Create a note for a case")
32 -async def create_case_note_route(case_id: int, note_creation_body: NoteCreationBody) -> NoteCreationResponse:
33 - verify_case_exists(case_id)
34 - logger.info(f"Creating a note for case {case_id}")
35 - return create_case_note(case_id, note_creation_body)
backend/app/connectors/dfir_iris/routes/users.py deleted
-39
@@ -1,39 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import Depends
3 -from fastapi import HTTPException
4 -from loguru import logger
5 -
6 -from app.connectors.dfir_iris.schema.alerts import AlertResponse
7 -from app.connectors.dfir_iris.schema.users import User
8 -from app.connectors.dfir_iris.schema.users import UsersResponse
9 -from app.connectors.dfir_iris.services.users import assign_user_to_alert
10 -from app.connectors.dfir_iris.services.users import get_users
11 -from app.connectors.dfir_iris.utils.universal import check_alert_exists
12 -from app.connectors.dfir_iris.utils.universal import check_user_exists
13 -
14 -
15 -def verify_user_exists(user_id: int) -> int:
16 - if not check_user_exists(user_id):
17 - raise HTTPException(status_code=400, detail=f"User {user_id} does not exist.")
18 - return user_id
19 -
20 -
21 -def verify_alert_exists(alert_id: str) -> str:
22 - if not check_alert_exists(alert_id):
23 - raise HTTPException(status_code=400, detail=f"Alert {alert_id} does not exist.")
24 - return alert_id
25 -
26 -
27 -dfir_iris_users_router = APIRouter()
28 -
29 -
30 -@dfir_iris_users_router.get("", response_model=UsersResponse, description="Get all users")
31 -async def get_all_users() -> UsersResponse:
32 - logger.info("Fetching all users")
33 - return get_users()
34 -
35 -
36 -@dfir_iris_users_router.post("/assign/{alert_id}/{user_id}", response_model=AlertResponse, description="Assign a user to an alert")
37 -async def assign_user_to_alert_route(alert_id: str = Depends(verify_alert_exists), user_id: int = Depends(verify_user_exists)) -> User:
38 - logger.info(f"Assigning user {user_id} to alert {alert_id}")
39 - return assign_user_to_alert(alert_id, user_id)
backend/app/connectors/dfir_iris/schema/alerts.py deleted
-25
@@ -1,25 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import List
4 -from typing import Optional
5 -
6 -from pydantic import BaseModel
7 -from pydantic import Field
8 -
9 -
10 -class AlertsResponse(BaseModel):
11 - alerts: Optional[List[Dict[str, Any]]] = Field([], description="The alerts returned from the search.")
12 - message: str
13 - success: bool
14 -
15 -
16 -class AlertResponse(BaseModel):
17 - alert: Optional[Dict[str, Any]] = Field({}, description="The alert returned from the search.")
18 - message: str
19 - success: bool
20 -
21 -
22 -class BookmarkedAlertsResponse(BaseModel):
23 - bookmarked_alerts: Optional[List[Dict[str, Any]]] = Field([], description="The alerts returned from the search.")
24 - message: str
25 - success: bool
backend/app/connectors/dfir_iris/schema/assets.py deleted
-40
@@ -1,40 +0,0 @@
1 -from typing import List
2 -from typing import Optional
3 -
4 -from pydantic import BaseModel
5 -
6 -
7 -class AssetState(BaseModel):
8 - object_last_update: str
9 - object_state: int
10 -
11 -
12 -class Asset(BaseModel):
13 - analysis_status: str
14 - analysis_status_id: int
15 - asset_compromise_status_id: int
16 - asset_description: str
17 - asset_domain: str
18 - asset_icon_compromised: str
19 - asset_icon_not_compromised: str
20 - asset_id: int
21 - asset_ip: str
22 - asset_name: str
23 - asset_tags: str
24 - asset_type: str
25 - asset_type_id: int
26 - asset_uuid: str
27 - ioc_links: Optional[None]
28 - link: List
29 -
30 -
31 -class AssetData(BaseModel):
32 - assets: List[Asset]
33 - state: AssetState
34 -
35 -
36 -class AssetResponse(BaseModel):
37 - assets: List[Asset]
38 - state: AssetState
39 - message: str
40 - success: bool
backend/app/connectors/dfir_iris/schema/cases.py deleted
-97
@@ -1,97 +0,0 @@
1 -from datetime import timedelta
2 -from enum import Enum
3 -from typing import Dict
4 -from typing import List
5 -from typing import Optional
6 -from typing import Union
7 -
8 -from pydantic import BaseModel
9 -from pydantic import Field
10 -
11 -
12 -class CaseModel(BaseModel):
13 - access_level: int
14 - case_close_date: str
15 - case_description: str
16 - case_id: int
17 - case_name: str
18 - case_open_date: str
19 - case_soc_id: str
20 - case_uuid: str
21 - classification: Optional[str]
22 - classification_id: Optional[int]
23 - client_name: str
24 - opened_by: str
25 - opened_by_user_id: int
26 - owner: str
27 - owner_id: int
28 - state_id: int
29 - state_name: str
30 -
31 -
32 -class CaseResponse(BaseModel):
33 - cases: List[CaseModel]
34 - message: str
35 - success: bool
36 -
37 -
38 -class ModificationHistoryItem(BaseModel):
39 - action: str
40 - user: str
41 - user_id: int
42 -
43 -
44 -class SingleCaseModel(BaseModel):
45 - case_description: str
46 - case_id: int
47 - case_name: str
48 - case_soc_id: str
49 - case_tags: Optional[str]
50 - case_uuid: str
51 - classification: Optional[Union[str, None]]
52 - classification_id: Optional[Union[int, None]]
53 - close_date: Optional[Union[str, None]]
54 - custom_attributes: Optional[Union[str, None]]
55 - customer_id: int
56 - customer_name: str
57 - initial_date: str
58 - modification_history: Dict[str, ModificationHistoryItem]
59 - open_by_user: str
60 - open_by_user_id: int
61 - open_date: str
62 - owner: str
63 - owner_id: int
64 - protagonists: List[str]
65 - reviewer: Optional[Union[str, None]]
66 - reviewer_id: Optional[Union[int, None]]
67 - state_id: int
68 - state_name: str
69 - status_id: int
70 - status_name: str
71 -
72 -
73 -class SingleCaseBody(BaseModel):
74 - case_id: int
75 -
76 -
77 -class SingleCaseResponse(BaseModel):
78 - case: SingleCaseModel
79 - message: str
80 - success: bool
81 -
82 -
83 -class TimeUnit(str, Enum):
84 - HOURS = "hours"
85 - DAYS = "days"
86 - WEEKS = "weeks"
87 -
88 -
89 -class CaseOlderThanBody(BaseModel):
90 - older_than: timedelta = Field(..., description="Amount of time to filter cases by")
91 - time_unit: TimeUnit
92 -
93 -
94 -class CasesBreachedResponse(BaseModel):
95 - cases_breached: List[CaseModel]
96 - message: str
97 - success: bool
backend/app/connectors/dfir_iris/schema/notes.py deleted
-68
@@ -1,68 +0,0 @@
1 -from typing import Dict
2 -from typing import List
3 -from typing import Optional
4 -
5 -from pydantic import BaseModel
6 -from pydantic import Field
7 -
8 -
9 -class CustomAttributes(BaseModel):
10 - # Define additional fields if custom_attributes contains specific keys
11 - pass
12 -
13 -
14 -class NoteDetails(BaseModel):
15 - custom_attributes: CustomAttributes
16 - group_id: int
17 - group_title: str
18 - group_uuid: str
19 - note_content: str
20 - note_creationdate: str
21 - note_id: int
22 - note_lastupdate: str
23 - note_title: str
24 - note_uuid: str
25 -
26 -
27 -class NoteDetailsResponse(BaseModel):
28 - note_details: NoteDetails
29 - message: str
30 - success: bool
31 -
32 -
33 -class NoteItem(BaseModel):
34 - note_details: NoteDetails
35 - note_id: int
36 - note_title: str
37 -
38 -
39 -class NotesResponse(BaseModel):
40 - notes: List[NoteItem]
41 - message: str
42 - success: bool
43 -
44 -
45 -class NotesQueryParams(BaseModel):
46 - case_id: int
47 - search_term: Optional[str] = Field("%", description="Search term to filter notes by. Defaults to wildcard search (%).")
48 -
49 -
50 -class NoteCreationBody(BaseModel):
51 - note_title: str = Field(..., description="Title of the note to be created.")
52 - note_content: str = Field(..., description="Content of the note to be created.")
53 -
54 -
55 -class NoteAttributes(BaseModel):
56 - custom_attributes: Dict[str, str] = Field(...)
57 - note_content: str = Field(...)
58 - note_creationdate: str = Field(...)
59 - note_id: int = Field(...)
60 - note_lastupdate: str = Field(...)
61 - note_title: str = Field(...)
62 - note_uuid: str = Field(...)
63 -
64 -
65 -class NoteCreationResponse(BaseModel):
66 - message: str = Field(...)
67 - note: NoteAttributes = Field(...)
68 - success: bool = Field(...)
backend/app/connectors/dfir_iris/schema/users.py deleted
-17
@@ -1,17 +0,0 @@
1 -from typing import List
2 -
3 -from pydantic import BaseModel
4 -
5 -
6 -class User(BaseModel):
7 - user_active: bool
8 - user_id: int
9 - user_login: str
10 - user_name: str
11 - user_uuid: str
12 -
13 -
14 -class UsersResponse(BaseModel):
15 - message: str
16 - success: bool
17 - users: List[User]
backend/app/connectors/dfir_iris/services/alerts.py deleted
-30
@@ -1,30 +0,0 @@
1 -from app.connectors.dfir_iris.schema.alerts import AlertResponse
2 -from app.connectors.dfir_iris.schema.alerts import AlertsResponse
3 -from app.connectors.dfir_iris.schema.alerts import BookmarkedAlertsResponse
4 -from app.connectors.dfir_iris.utils.universal import fetch_and_validate_data
5 -from app.connectors.dfir_iris.utils.universal import initialize_client_and_alert
6 -
7 -
8 -def get_alerts() -> AlertsResponse:
9 - client, alert = initialize_client_and_alert("DFIR-IRIS")
10 - result = fetch_and_validate_data(client, alert.filter_alerts)
11 - return AlertsResponse(success=True, message="Successfully fetched alerts", alerts=result["data"]["alerts"])
12 -
13 -
14 -def bookmark_alert(alert_id: str, bookmarked: bool) -> AlertResponse:
15 - client, alert = initialize_client_and_alert("DFIR-IRIS")
16 - if bookmarked:
17 - result = fetch_and_validate_data(client, alert.update_alert, alert_id, {"alert_tags": "bookmarked"})
18 - return AlertResponse(success=True, message="Successfully bookmarked alert", alert=result["data"])
19 - result = fetch_and_validate_data(client, alert.update_alert, alert_id, {"alert_tags": ""})
20 - return AlertResponse(success=True, message="Successfully removed bookmark from alert", alert=result["data"])
21 -
22 -
23 -def get_bookmarked_alerts() -> BookmarkedAlertsResponse:
24 - alerts = get_alerts().alerts
25 - bookmarked_alerts = []
26 - for alert in alerts:
27 - if alert["alert_tags"] is not None and "bookmarked" in alert["alert_tags"]:
28 - bookmarked_alerts.append(alert)
29 -
30 - return BookmarkedAlertsResponse(success=True, message="Successfully fetched bookmarked alerts", bookmarked_alerts=bookmarked_alerts)
backend/app/connectors/dfir_iris/services/assets.py deleted
-20
@@ -1,20 +0,0 @@
1 -from app.connectors.dfir_iris.schema.assets import Asset
2 -from app.connectors.dfir_iris.schema.assets import AssetResponse
3 -from app.connectors.dfir_iris.schema.assets import AssetState
4 -from app.connectors.dfir_iris.utils.universal import fetch_and_validate_data
5 -from app.connectors.dfir_iris.utils.universal import initialize_client_and_case
6 -
7 -
8 -def get_case_assets(case_id: int) -> AssetResponse:
9 - client, case = initialize_client_and_case("DFIR-IRIS")
10 - result = fetch_and_validate_data(client, case.list_assets, case_id)
11 -
12 - asset_list = result["data"]["assets"]
13 - state_data = result["data"]["state"]
14 -
15 - return AssetResponse(
16 - success=True,
17 - message="Successfully fetched assets for case",
18 - assets=[Asset(**asset) for asset in asset_list], # List[Asset]
19 - state=AssetState(**state_data), # AssetState
20 - )
backend/app/connectors/dfir_iris/services/cases.py deleted
-97
@@ -1,97 +0,0 @@
1 -from datetime import datetime
2 -from typing import Dict
3 -from typing import List
4 -
5 -from dfir_iris_client.case import Case
6 -from fastapi import HTTPException
7 -from loguru import logger
8 -
9 -from app.connectors.dfir_iris.schema.cases import CaseOlderThanBody
10 -from app.connectors.dfir_iris.schema.cases import CaseResponse
11 -from app.connectors.dfir_iris.schema.cases import CasesBreachedResponse
12 -from app.connectors.dfir_iris.schema.cases import SingleCaseBody
13 -from app.connectors.dfir_iris.schema.cases import SingleCaseResponse
14 -from app.connectors.dfir_iris.utils.universal import create_dfir_iris_client
15 -from app.connectors.dfir_iris.utils.universal import fetch_and_parse_data
16 -
17 -
18 -def get_client_and_cases() -> Dict:
19 - """
20 - Initialize the client session and fetch all cases.
21 -
22 - Returns:
23 - Dictionary containing the success status and either the case data or an error message.
24 - """
25 - dfir_iris_client = create_dfir_iris_client("DFIR-IRIS")
26 - case = Case(session=dfir_iris_client)
27 - logger.info("Fetching all cases after getting session")
28 - result = fetch_and_parse_data(dfir_iris_client, case.list_cases)
29 - return result
30 -
31 -
32 -def filter_open_cases(cases: List[Dict]) -> List[Dict]:
33 - """
34 - Filters out cases that are still open.
35 -
36 - Args:
37 - cases (List): List of all cases.
38 -
39 - Returns:
40 - List of cases that are still open.
41 - """
42 - return [case for case in cases if case["case_close_date"] == ""]
43 -
44 -
45 -def filter_cases_older_than(cases: List[Dict], older_than: datetime) -> List[Dict]:
46 - """
47 - Filters out cases that are older than the specified time.
48 -
49 - Args:
50 - cases (List): List of all cases.
51 - older_than (datetime): The datetime to filter by.
52 -
53 - Returns:
54 - List of cases that are older than the specified time.
55 - """
56 - current_time = datetime.now()
57 - filtered_cases = []
58 - for case in cases:
59 - case_open_date = (
60 - datetime.strptime(case["case_open_date"], "%m/%d/%Y")
61 - if not isinstance(case["case_open_date"], datetime)
62 - else case["case_open_date"]
63 - )
64 - if case_open_date < current_time - older_than:
65 - case["case_open_date"] = case_open_date.strftime("%m/%d/%Y") # Convert back to string to match the model
66 - filtered_cases.append(case)
67 - return filtered_cases
68 -
69 -
70 -def get_all_cases() -> CaseResponse:
71 - result = get_client_and_cases()
72 - if not result["success"]:
73 - logger.error(f"Failed to get all cases: {result['message']}")
74 - return HTTPException(status_code=500, detail=f"Failed to get all cases: {result['message']}")
75 - return CaseResponse(success=True, message="Successfully fetched all cases", cases=result["data"])
76 -
77 -
78 -def get_cases_older_than(case_older_than_body: CaseOlderThanBody) -> CasesBreachedResponse:
79 - result = get_client_and_cases()
80 - if not result["success"]:
81 - logger.error(f"Failed to get all cases: {result['message']}")
82 - return HTTPException(status_code=500, detail=f"Failed to get all cases: {result['message']}")
83 -
84 - open_cases = filter_open_cases(result["data"])
85 - breached_cases = filter_cases_older_than(open_cases, case_older_than_body.older_than)
86 - return CasesBreachedResponse(
87 - success=True,
88 - message=f"Successfully fetched all cases older than {case_older_than_body.older_than}",
89 - cases_breached=breached_cases,
90 - )
91 -
92 -
93 -def get_single_case(case_id: SingleCaseBody) -> SingleCaseResponse:
94 - dfir_iris_client = create_dfir_iris_client("DFIR-IRIS")
95 - case = Case(session=dfir_iris_client)
96 - result = fetch_and_parse_data(dfir_iris_client, case.get_case, case_id)
97 - return SingleCaseResponse(success=True, message="Successfully fetched single case", case=result["data"])
backend/app/connectors/dfir_iris/services/notes.py deleted
-59
@@ -1,59 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import List
4 -
5 -from dfir_iris_client.case import Case
6 -from loguru import logger
7 -
8 -from app.connectors.dfir_iris.schema.notes import NoteCreationBody
9 -from app.connectors.dfir_iris.schema.notes import NoteCreationResponse
10 -from app.connectors.dfir_iris.schema.notes import NoteDetails
11 -from app.connectors.dfir_iris.schema.notes import NoteDetailsResponse
12 -from app.connectors.dfir_iris.schema.notes import NotesResponse
13 -from app.connectors.dfir_iris.utils.universal import fetch_and_validate_data
14 -from app.connectors.dfir_iris.utils.universal import initialize_client_and_case
15 -
16 -
17 -def process_notes(notes: List[Dict], case_id: int) -> List[Dict]:
18 - processed_notes = []
19 - for note in notes:
20 - note_details = get_case_note_details(note["note_id"], case_id)
21 - logger.info(f"Note details: {note_details}")
22 - note["note_details"] = note_details.note_details
23 - processed_notes.append(note)
24 - return processed_notes
25 -
26 -
27 -def get_case_notes(case_id: int, search_term: str) -> NotesResponse:
28 - client, case = initialize_client_and_case("DFIR-IRIS")
29 - result = fetch_and_validate_data(client, case.search_notes, search_term, case_id)
30 - processed_notes = process_notes(result["data"], case_id)
31 - return NotesResponse(success=True, message="Successfully fetched notes for case", notes=processed_notes)
32 -
33 -
34 -def get_case_note_details(note_id: int, case_id: int) -> NoteDetailsResponse:
35 - client, case = initialize_client_and_case("DFIR-IRIS")
36 - result = fetch_and_validate_data(client, case.get_note, note_id, case_id)
37 - note_details = NoteDetails(**result["data"])
38 - return NoteDetailsResponse(success=True, message="Successfully fetched note details", note_details=note_details)
39 -
40 -
41 -def perform_note_creation(client: Any, case: Case, note_creation_body: NoteCreationBody, case_id: int) -> Dict:
42 - result = fetch_and_validate_data(client, case.add_notes_group, note_creation_body.note_title, case_id)
43 - note_id = result["data"]["group_id"]
44 - custom_attributes = {}
45 - return fetch_and_validate_data(
46 - client,
47 - case.add_note,
48 - note_creation_body.note_title,
49 - note_creation_body.note_content,
50 - note_id,
51 - custom_attributes,
52 - case_id,
53 - )
54 -
55 -
56 -def create_case_note(case_id: int, note_creation_body: NoteCreationBody) -> NoteCreationResponse:
57 - client, case = initialize_client_and_case("DFIR-IRIS")
58 - result = perform_note_creation(client, case, note_creation_body, case_id)
59 - return NoteCreationResponse(success=True, message="Successfully created note", note=result["data"])
backend/app/connectors/dfir_iris/services/users.py deleted
-17
@@ -1,17 +0,0 @@
1 -from app.connectors.dfir_iris.schema.alerts import AlertResponse
2 -from app.connectors.dfir_iris.schema.users import UsersResponse
3 -from app.connectors.dfir_iris.utils.universal import fetch_and_validate_data
4 -from app.connectors.dfir_iris.utils.universal import initialize_client_and_alert
5 -from app.connectors.dfir_iris.utils.universal import initialize_client_and_user
6 -
7 -
8 -def get_users() -> UsersResponse:
9 - client, user = initialize_client_and_user("DFIR-IRIS")
10 - result = fetch_and_validate_data(client, user.list_users)
11 - return UsersResponse(success=True, message="Successfully fetched users", users=result["data"])
12 -
13 -
14 -def assign_user_to_alert(alert_id: str, user_id: int) -> AlertResponse:
15 - client, alert = initialize_client_and_alert("DFIR-IRIS")
16 - result = fetch_and_validate_data(client, alert.update_alert, alert_id, {"alert_owner_id": user_id})
17 - return AlertResponse(success=True, message="Successfully assigned user to alert", alert=result["data"])
backend/app/connectors/dfir_iris/utils/universal.py deleted
-197
@@ -1,197 +0,0 @@
1 -from typing import Any
2 -from typing import Callable
3 -from typing import Dict
4 -from typing import Optional
5 -from typing import Tuple
6 -from typing import Union
7 -
8 -import requests
9 -from dfir_iris_client.alert import Alert
10 -from dfir_iris_client.case import Case
11 -from dfir_iris_client.helper.utils import assert_api_resp
12 -from dfir_iris_client.helper.utils import get_data_from_resp
13 -from dfir_iris_client.session import ClientSession
14 -from dfir_iris_client.users import User
15 -from fastapi import HTTPException
16 -from loguru import logger
17 -
18 -from app.connectors.utils import get_connector_info_from_db
19 -
20 -
21 -def verify_dfir_iris_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
22 - """
23 - Verifies the connection to DFIR-IRIS service.
24 -
25 - Returns:
26 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
27 - """
28 - logger.info(f"Verifying the DFIR-IRIS connection to {attributes['connector_url']}")
29 -
30 - try:
31 - headers = {
32 - "Authorization": f"Bearer {attributes['connector_api_key']}",
33 - }
34 - dfir_iris = requests.get(
35 - f"{attributes['connector_url']}/api/ping",
36 - headers=headers,
37 - verify=False,
38 - )
39 - # See if 200 is returned
40 - if dfir_iris.status_code == 200:
41 - logger.info(
42 - f"Connection to {attributes['connector_url']} successful",
43 - )
44 - logger.debug("DFIR-IRIS connection successful")
45 - return {"connectionSuccessful": True, "message": "DFIR-IRIS connection successful"}
46 - except Exception as e:
47 - logger.error(f"Connection to {attributes['connector_url']} failed with error: {e}")
48 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error: {e}"}
49 -
50 -
51 -def verify_dfir_iris_connection(connector_name: str) -> str:
52 - """
53 - Returns the authentication token for the DFIR-IRIS service.
54 -
55 - Returns:
56 - str: Authentication token for the DFIR-IRIS service.
57 - """
58 - attributes = get_connector_info_from_db(connector_name)
59 - if attributes is None:
60 - logger.error("No DFIR-IRIS connector found in the database")
61 - return None
62 - return verify_dfir_iris_credentials(attributes)
63 -
64 -
65 -def create_dfir_iris_client(connector_name: str) -> ClientSession:
66 - """
67 - Creates a session with DFIR-IRIS.
68 -
69 - This method creates a session with DFIR-IRIS and returns a dictionary with a success status and the session object.
70 - If a session cannot be established, an error is logged and a dictionary with "success" set to False and an error message is
71 - returned.
72 -
73 - Returns:
74 - dict: A dictionary containing the success status and either the session object or an error message.
75 - """
76 - try:
77 - attributes = get_connector_info_from_db(connector_name)
78 - logger.info("Creating session with DFIR-IRIS.")
79 - return ClientSession(
80 - host=attributes["connector_url"],
81 - apikey=attributes["connector_api_key"],
82 - agent="iris-client",
83 - ssl_verify=False,
84 - timeout=120,
85 - proxy=None,
86 - )
87 - except Exception as e:
88 - logger.error(f"Error creating session with DFIR-IRIS: {e}")
89 - return HTTPException(status_code=500, detail=f"Error creating session with DFIR-IRIS: {e}")
90 -
91 -
92 -def fetch_and_parse_data(session: ClientSession, action: Callable, *args) -> Dict[str, Union[bool, Optional[Dict]]]:
93 - """
94 - Fetches and parses data from DFIR-IRIS using a specified action.
95 -
96 - Args:
97 - session (ClientSession): The DFIR-IRIS session object.
98 - action (Callable): The function to execute to fetch data from DFIR-IRIS. This function should accept *args.
99 - args: The arguments to pass to the action function.
100 -
101 - Returns:
102 - dict: A dictionary containing the success status and either the fetched data or None if the operation was unsuccessful.
103 - """
104 - try:
105 - logger.info(f"Executing {action.__name__}... on args: {args}")
106 - status = action(*args)
107 - assert_api_resp(status, soft_fail=False)
108 - data = get_data_from_resp(status)
109 - logger.info(f"Successfully executed {action.__name__}")
110 - return {"success": True, "data": data}
111 - except Exception as err:
112 - logger.error(f"Failed to execute {action.__name__}: {err}")
113 - return HTTPException(status_code=500, detail=f"Failed to execute {action.__name__}: {err}")
114 -
115 -
116 -def initialize_client_and_case(service_name: str) -> Tuple[Any, Case]:
117 - dfir_iris_client = create_dfir_iris_client(service_name)
118 - case = Case(session=dfir_iris_client)
119 - return dfir_iris_client, case
120 -
121 -
122 -def initialize_client_and_alert(service_name: str) -> Tuple[Any, Alert]:
123 - dfir_iris_client = create_dfir_iris_client(service_name)
124 - alert = Alert(session=dfir_iris_client)
125 - return dfir_iris_client, alert
126 -
127 -
128 -def initialize_client_and_user(service_name: str) -> Tuple[Any, Alert]:
129 - dfir_iris_client = create_dfir_iris_client(service_name)
130 - user = User(session=dfir_iris_client)
131 - return dfir_iris_client, user
132 -
133 -
134 -def handle_error(error_message: str, status_code: int = 500):
135 - logger.error(error_message)
136 - raise HTTPException(status_code=status_code, detail=error_message)
137 -
138 -
139 -def fetch_and_validate_data(client: Any, func: Callable, *args: Any) -> Dict:
140 - result = fetch_and_parse_data(client, func, *args)
141 - if not result["success"]:
142 - handle_error(f"Failed to fetch data: {result['message']}")
143 - return result
144 -
145 -
146 -def check_case_exists(case_id: int) -> bool:
147 - try:
148 - logger.info(f"Checking if case {case_id} exists")
149 - dfir_iris_client = create_dfir_iris_client("DFIR-IRIS")
150 - case = Case(session=dfir_iris_client)
151 - data = case.get_case(case_id)
152 - assert_api_resp(data, soft_fail=False)
153 - result = get_data_from_resp(data)
154 - if result is None:
155 - logger.info(f"Case {case_id} does not exist")
156 - return False
157 - logger.info(f"Case {case_id} exists")
158 - return True
159 - except Exception as e:
160 - logger.error(f"Failed to check if case {case_id} exists: {e}")
161 - return False
162 -
163 -
164 -def check_alert_exists(alert_id: str) -> bool:
165 - try:
166 - logger.info(f"Checking if alert {alert_id} exists")
167 - dfir_iris_client = create_dfir_iris_client("DFIR-IRIS")
168 - alert = Alert(session=dfir_iris_client)
169 - data = alert.get_alert(alert_id)
170 - assert_api_resp(data, soft_fail=False)
171 - result = get_data_from_resp(data)
172 - if result is None:
173 - logger.info(f"Alert {alert_id} does not exist")
174 - return False
175 - logger.info(f"Alert {alert_id} exists")
176 - return True
177 - except Exception as e:
178 - logger.error(f"Failed to check if alert {alert_id} exists: {e}")
179 - return False
180 -
181 -
182 -def check_user_exists(user_id: int) -> bool:
183 - try:
184 - logger.info(f"Checking if user {user_id} exists")
185 - dfir_iris_client = create_dfir_iris_client("DFIR-IRIS")
186 - user = User(session=dfir_iris_client)
187 - data = user.get_user(user_id)
188 - assert_api_resp(data, soft_fail=False)
189 - result = get_data_from_resp(data)
190 - if result is None:
191 - logger.info(f"User {user_id} does not exist")
192 - return False
193 - logger.info(f"User {user_id} exists")
194 - return True
195 - except Exception as e:
196 - logger.error(f"Failed to check if user {user_id} exists: {e}")
197 - return False
backend/app/connectors/graylog/routes/collector.py deleted
-40
@@ -1,40 +0,0 @@
1 -from fastapi import APIRouter
2 -from loguru import logger
3 -
4 -from app.connectors.graylog.schema.collector import ConfiguredInputsResponse
5 -from app.connectors.graylog.schema.collector import GraylogIndicesResponse
6 -from app.connectors.graylog.schema.collector import GraylogInputsResponse
7 -from app.connectors.graylog.schema.collector import RunningInputsResponse
8 -from app.connectors.graylog.services.collector import get_indices_full
9 -from app.connectors.graylog.services.collector import get_inputs
10 -from app.connectors.graylog.services.collector import get_inputs_configured
11 -from app.connectors.graylog.services.collector import get_inputs_running
12 -
13 -# App specific imports
14 -
15 -
16 -graylog_collector_router = APIRouter()
17 -
18 -
19 -@graylog_collector_router.get("/indices", response_model=GraylogIndicesResponse, description="Get all indices")
20 -async def get_all_indices() -> GraylogIndicesResponse:
21 - logger.info("Fetching all graylog indices")
22 - return get_indices_full()
23 -
24 -
25 -@graylog_collector_router.get("/inputs", response_model=GraylogInputsResponse, description="Get all inputs")
26 -async def get_all_inputs() -> GraylogInputsResponse:
27 - logger.info("Fetching all graylog inputs")
28 - return get_inputs()
29 -
30 -
31 -@graylog_collector_router.get("/inputs/running", response_model=RunningInputsResponse, description="Get all running inputs")
32 -async def get_all_running_inputs() -> RunningInputsResponse:
33 - logger.info("Fetching all graylog running inputs")
34 - return get_inputs_running()
35 -
36 -
37 -@graylog_collector_router.get("/inputs/configured", response_model=ConfiguredInputsResponse, description="Get all configured inputs")
38 -async def get_all_configured_inputs() -> ConfiguredInputsResponse:
39 - logger.info("Fetching all graylog configured inputs")
40 - return get_inputs_configured()
backend/app/connectors/graylog/routes/events.py deleted
-25
@@ -1,25 +0,0 @@
1 -from fastapi import APIRouter
2 -from loguru import logger
3 -
4 -from app.connectors.graylog.schema.events import AlertQuery
5 -from app.connectors.graylog.schema.events import GraylogAlertsResponse
6 -from app.connectors.graylog.schema.events import GraylogEventDefinitionsResponse
7 -from app.connectors.graylog.services.events import get_alerts
8 -from app.connectors.graylog.services.events import get_event_definitions
9 -
10 -# App specific imports
11 -
12 -
13 -graylog_events_router = APIRouter()
14 -
15 -
16 -@graylog_events_router.get("/event/definitions", response_model=GraylogEventDefinitionsResponse, description="Get all event definitions")
17 -async def get_all_event_definitions() -> GraylogEventDefinitionsResponse:
18 - logger.info("Fetching all graylog event definitions")
19 - return get_event_definitions()
20 -
21 -
22 -@graylog_events_router.post("/event/alerts", response_model=GraylogAlertsResponse, description="Get all alerts")
23 -async def get_all_alerts(alert_query: AlertQuery) -> GraylogAlertsResponse:
24 - logger.info("Fetching all graylog alerts")
25 - return get_alerts(alert_query)
backend/app/connectors/graylog/routes/management.py deleted
-110
@@ -1,110 +0,0 @@
1 -from typing import List
2 -
3 -from fastapi import APIRouter
4 -from fastapi import Depends
5 -from fastapi import HTTPException
6 -from loguru import logger
7 -
8 -from app.connectors.graylog.schema.management import DeletedIndexBody
9 -from app.connectors.graylog.schema.management import DeletedIndexResponse
10 -from app.connectors.graylog.schema.management import StartInputBody
11 -from app.connectors.graylog.schema.management import StartInputResponse
12 -from app.connectors.graylog.schema.management import StartStreamBody
13 -from app.connectors.graylog.schema.management import StartStreamResponse
14 -from app.connectors.graylog.schema.management import StopInputBody
15 -from app.connectors.graylog.schema.management import StopInputResponse
16 -from app.connectors.graylog.schema.management import StopStreamBody
17 -from app.connectors.graylog.schema.management import StopStreamResponse
18 -from app.connectors.graylog.services.collector import get_index_names
19 -from app.connectors.graylog.services.collector import get_input_ids
20 -from app.connectors.graylog.services.management import delete_index
21 -from app.connectors.graylog.services.management import start_input
22 -from app.connectors.graylog.services.management import start_stream
23 -from app.connectors.graylog.services.management import stop_input
24 -from app.connectors.graylog.services.management import stop_stream
25 -from app.connectors.graylog.services.streams import get_stream_ids
26 -
27 -graylog_management_router = APIRouter()
28 -
29 -
30 -def get_managed_index_names() -> List[str]:
31 - return get_index_names()
32 -
33 -
34 -def get_managed_input_ids() -> List[str]:
35 - return get_input_ids()
36 -
37 -
38 -def get_managed_stream_ids() -> List[str]:
39 - return get_stream_ids()
40 -
41 -
42 -def verify_index_name(deleted_index_body: DeletedIndexBody) -> DeletedIndexBody:
43 - # Remove any extra spaces from index_name
44 - deleted_index_body.index_name = deleted_index_body.index_name.strip()
45 -
46 - managed_index_names = get_managed_index_names()
47 - if deleted_index_body.index_name not in managed_index_names:
48 - raise HTTPException(
49 - status_code=400,
50 - detail=f"Index name '{deleted_index_body.index_name}' is not managed by Graylog or no longer exists.",
51 - )
52 - return deleted_index_body
53 -
54 -
55 -def verify_input_id(stop_input_body: StopInputBody) -> StopInputBody:
56 - # Remove any extra spaces from input_id
57 - stop_input_body.input_id = stop_input_body.input_id.strip()
58 -
59 - managed_input_ids = get_managed_input_ids()
60 - if stop_input_body.input_id not in managed_input_ids:
61 - raise HTTPException(status_code=400, detail=f"Input ID '{stop_input_body.input_id}' is not managed by Graylog or no longer exists.")
62 - return stop_input_body
63 -
64 -
65 -def verify_stream_id(stop_stream_body: StopStreamBody) -> StopStreamBody:
66 - # Remove any extra spaces from stream_id
67 - stop_stream_body.stream_id = stop_stream_body.stream_id.strip()
68 -
69 - managed_stream_ids = get_managed_stream_ids()
70 - if stop_stream_body.stream_id not in managed_stream_ids:
71 - raise HTTPException(
72 - status_code=400,
73 - detail=f"Stream ID '{stop_stream_body.stream_id}' is not managed by Graylog or no longer exists.",
74 - )
75 - return stop_stream_body
76 -
77 -
78 -@graylog_management_router.delete("/index", response_model=DeletedIndexResponse, description="Delete index")
79 -async def delete_index_route(deleted_index_body: DeletedIndexBody = Depends(verify_index_name)) -> DeletedIndexResponse:
80 - logger.info(f"Deleting index {deleted_index_body.index_name}")
81 -
82 - return delete_index(deleted_index_body.index_name)
83 -
84 -
85 -@graylog_management_router.post("/input/stop", response_model=StopInputResponse, description="Stop input")
86 -async def stop_input_route(stop_input_body: StopInputBody = Depends(verify_input_id)) -> StopInputResponse:
87 - logger.info(f"Stopping input {stop_input_body.input_id}")
88 -
89 - return stop_input(stop_input_body.input_id)
90 -
91 -
92 -@graylog_management_router.post("/input/start", response_model=StartInputResponse, description="Start input")
93 -async def start_input_route(start_input_body: StartInputBody = Depends(verify_input_id)) -> StartInputResponse:
94 - logger.info(f"Starting input {start_input_body.input_id}")
95 -
96 - return start_input(start_input_body.input_id)
97 -
98 -
99 -@graylog_management_router.post("/stream/stop", response_model=StopStreamResponse, description="Stop stream")
100 -async def stop_stream_route(stop_stream_body: StopStreamBody = Depends(verify_stream_id)) -> StopStreamResponse:
101 - logger.info(f"Stopping stream {stop_stream_body.stream_id}")
102 -
103 - return stop_stream(stop_stream_body.stream_id)
104 -
105 -
106 -@graylog_management_router.post("/stream/start", response_model=StartStreamResponse, description="Start stream")
107 -async def start_stream_route(start_stream_body: StartStreamBody = Depends(verify_stream_id)) -> StartStreamResponse:
108 - logger.info(f"Starting stream {start_stream_body.stream_id}")
109 -
110 - return start_stream(start_stream_body.stream_id)
backend/app/connectors/graylog/routes/monitoring.py deleted
-25
@@ -1,25 +0,0 @@
1 -from fastapi import APIRouter
2 -from loguru import logger
3 -
4 -from app.connectors.graylog.schema.monitoring import GraylogMessagesResponse
5 -from app.connectors.graylog.schema.monitoring import GraylogMetricsResponse
6 -from app.connectors.graylog.services.monitoring import get_messages
7 -from app.connectors.graylog.services.monitoring import get_metrics
8 -
9 -# App specific imports
10 -
11 -
12 -graylog_monitoring_router = APIRouter()
13 -
14 -
15 -@graylog_monitoring_router.get("/messages", response_model=GraylogMessagesResponse, description="Get all messages")
16 -async def get_all_messages(page_number: int = 1) -> GraylogMessagesResponse:
17 - logger.info("Fetching all graylog messages")
18 - logger.info(f"Page number: {page_number}")
19 - return get_messages(page_number)
20 -
21 -
22 -@graylog_monitoring_router.get("/metrics", response_model=GraylogMetricsResponse, description="Get all metrics")
23 -async def get_all_metrics() -> GraylogMetricsResponse:
24 - logger.info("Fetching all graylog metrics")
25 - return get_metrics()
backend/app/connectors/graylog/routes/pipelines.py deleted
-24
@@ -1,24 +0,0 @@
1 -from fastapi import APIRouter
2 -from loguru import logger
3 -
4 -from app.connectors.graylog.schema.pipelines import GraylogPipelinesResponse
5 -from app.connectors.graylog.schema.pipelines import PipelineRulesResponse
6 -from app.connectors.graylog.services.pipelines import get_pipeline_rules
7 -from app.connectors.graylog.services.pipelines import get_pipelines
8 -
9 -# App specific imports
10 -
11 -
12 -graylog_pipelines_router = APIRouter()
13 -
14 -
15 -@graylog_pipelines_router.get("/pipelines", response_model=GraylogPipelinesResponse, description="Get all pipelines")
16 -async def get_all_pipelines() -> GraylogPipelinesResponse:
17 - logger.info("Fetching all graylog pipelines")
18 - return get_pipelines()
19 -
20 -
21 -@graylog_pipelines_router.get("/pipeline/rules", response_model=PipelineRulesResponse, description="Get all pipeline rules")
22 -async def get_all_pipeline_rules() -> PipelineRulesResponse:
23 - logger.info("Fetching all graylog pipeline rules")
24 - return get_pipeline_rules()
backend/app/connectors/graylog/routes/streams.py deleted
-16
@@ -1,16 +0,0 @@
1 -from fastapi import APIRouter
2 -from loguru import logger
3 -
4 -from app.connectors.graylog.schema.streams import GraylogStreamsResponse
5 -from app.connectors.graylog.services.streams import get_streams
6 -
7 -# App specific imports
8 -
9 -
10 -graylog_streams_router = APIRouter()
11 -
12 -
13 -@graylog_streams_router.get("/streams", response_model=GraylogStreamsResponse, description="Get all streams")
14 -async def get_all_streams() -> GraylogStreamsResponse:
15 - logger.info("Fetching all graylog streams")
16 - return get_streams()
backend/app/connectors/graylog/schema/collector.py deleted
-152
@@ -1,152 +0,0 @@
1 -from typing import Dict
2 -from typing import List
3 -from typing import Optional
4 -
5 -from pydantic import BaseModel
6 -from pydantic import Field
7 -
8 -
9 -class Document(BaseModel):
10 - count: int
11 - deleted: int
12 -
13 -
14 -class Operation(BaseModel):
15 - time_seconds: int
16 - total: int
17 -
18 -
19 -class ShardInfo(BaseModel):
20 - documents: Document
21 - flush: Operation
22 - get: Operation
23 - index: Operation
24 - merge: Operation
25 - open_search_contexts: int
26 - refresh: Operation
27 - search_fetch: Operation
28 - search_query: Operation
29 - segments: int
30 - store_size_bytes: int
31 -
32 -
33 -class Routing(BaseModel):
34 - active: bool
35 - id: int
36 - node_hostname: str
37 - node_id: str
38 - node_name: str
39 - primary: bool
40 - relocating_to: Optional[None] # Assuming this is always None based on your example
41 - state: str
42 -
43 -
44 -class IndexInfo(BaseModel):
45 - all_shards: ShardInfo
46 - primary_shards: ShardInfo
47 - reopened: bool
48 - routing: List[Routing]
49 -
50 -
51 -class GraylogIndexItem(BaseModel):
52 - index_name: str
53 - index_info: IndexInfo
54 -
55 -
56 -class GraylogIndicesResponse(BaseModel):
57 - indices: List[GraylogIndexItem]
58 - message: str
59 - success: bool
60 -
61 -
62 -class ConfiguredInputAttributes(BaseModel):
63 - recv_buffer_size: int
64 - tcp_keepalive: bool
65 - use_null_delimiter: bool
66 - number_worker_threads: int
67 - tls_client_auth_cert_file: Optional[str]
68 - force_rdns: Optional[bool]
69 - bind_address: str
70 - tls_cert_file: Optional[str]
71 - store_full_message: Optional[bool]
72 - expand_structured_data: Optional[bool]
73 - port: int
74 - tls_key_file: Optional[str]
75 - tls_enable: bool
76 - tls_key_password: Optional[str]
77 - max_message_size: int
78 - tls_client_auth: str
79 - override_source: Optional[str]
80 - charset_name: Optional[str]
81 - allow_override_date: Optional[bool]
82 -
83 -
84 -class ConfiguredInput(BaseModel):
85 - title: str
86 - global_field: bool = Field(alias="global")
87 - name: str
88 - content_pack: Optional[str]
89 - created_at: str
90 - type: str
91 - creator_user_id: str
92 - attributes: ConfiguredInputAttributes
93 - static_fields: Dict[str, str]
94 - node: str
95 - id: str
96 -
97 -
98 -class MessageInputAttributes(BaseModel):
99 - recv_buffer_size: int
100 - tcp_keepalive: bool
101 - use_null_delimiter: bool
102 - number_worker_threads: int
103 - tls_client_auth_cert_file: Optional[str]
104 - bind_address: str
105 - tls_cert_file: Optional[str]
106 - port: int
107 - tls_key_file: Optional[str]
108 - tls_enable: bool
109 - tls_key_password: Optional[str]
110 - max_message_size: int
111 - tls_client_auth: str
112 -
113 -
114 -class MessageInput(BaseModel):
115 - title: str
116 - global_field: bool = Field(alias="global")
117 - name: str
118 - content_pack: Optional[str]
119 - created_at: str
120 - type: str
121 - creator_user_id: str
122 - attributes: MessageInputAttributes
123 - static_fields: Dict[str, str]
124 - node: str
125 - id: str
126 -
127 -
128 -class RunningInput(BaseModel):
129 - id: str
130 - state: str
131 - started_at: str
132 - detailed_message: Optional[str]
133 - message_input: MessageInput
134 -
135 -
136 -class ConfiguredInputsResponse(BaseModel):
137 - configured_inputs: List[ConfiguredInput]
138 - message: str
139 - success: bool
140 -
141 -
142 -class RunningInputsResponse(BaseModel):
143 - running_inputs: List[RunningInput]
144 - message: str
145 - success: bool
146 -
147 -
148 -class GraylogInputsResponse(BaseModel):
149 - configured_inputs: List[ConfiguredInput]
150 - running_inputs: List[RunningInput]
151 - message: str
152 - success: bool
backend/app/connectors/graylog/schema/events.py deleted
-155
@@ -1,155 +0,0 @@
1 -from typing import Dict
2 -from typing import List
3 -from typing import Optional
4 -from typing import Union
5 -
6 -from pydantic import BaseModel
7 -
8 -
9 -class Provider(BaseModel):
10 - require_values: bool
11 - template: str
12 - type: str
13 -
14 -
15 -class FieldSpecItem(BaseModel):
16 - data_type: str
17 - providers: List[Provider]
18 -
19 -
20 -class Conditions(BaseModel):
21 - expression: Optional[str]
22 -
23 -
24 -class Config(BaseModel):
25 - conditions: Conditions
26 - execute_every_ms: int
27 - group_by: List[str]
28 - query: str
29 - query_parameters: List[str]
30 - search_within_ms: int
31 - series: List[str]
32 - streams: List[str]
33 - type: str
34 -
35 -
36 -class NotificationSettings(BaseModel):
37 - backlog_size: int
38 - grace_period_ms: int
39 -
40 -
41 -class Storage(BaseModel):
42 - streams: List[str]
43 - type: str
44 -
45 -
46 -class EventDefinition(BaseModel):
47 - _scope: str
48 - alert: bool
49 - config: Config
50 - description: str
51 - field_spec: Dict[str, FieldSpecItem]
52 - id: str
53 - key_spec: List[str]
54 - notification_settings: NotificationSettings
55 - notifications: Optional[List[Dict[str, Union[str, None]]]]
56 - priority: int
57 - storage: List[Storage]
58 - title: str
59 -
60 -
61 -class GraylogEventDefinitionsResponse(BaseModel):
62 - event_definitions: List[EventDefinition]
63 - message: str
64 - success: bool
65 -
66 -
67 -class AlertQuery(BaseModel):
68 - query: Optional[str] = ""
69 - page: int = 1
70 - per_page: int = 100
71 - filter: Optional[Dict[str, Union[str, List[str]]]] = {"alerts": "only", "event_definitions": []}
72 - timerange: Optional[Dict[str, Union[int, str]]] = {"range": 86400, "type": "relative"}
73 -
74 -
75 -class SimplifiedEventDefinition(BaseModel):
76 - description: str
77 - id: str
78 - title: str
79 -
80 -
81 -class Stream(BaseModel):
82 - description: str
83 - id: str
84 - title: str
85 -
86 -
87 -class Context(BaseModel):
88 - event_definitions: Dict[str, SimplifiedEventDefinition]
89 - streams: Dict[str, Stream]
90 -
91 -
92 -class Fields(BaseModel):
93 - test: str
94 -
95 -
96 -class Event(BaseModel):
97 - alert: bool
98 - event_definition_id: str
99 - event_definition_type: str
100 - fields: Fields
101 - group_by_fields: Dict[str, str]
102 - id: str
103 - key: Optional[str]
104 - key_tuple: List[str]
105 - message: str
106 - origin_context: str
107 - priority: int
108 - source: str
109 - source_streams: List[str]
110 - streams: List[str]
111 - timerange_end: Optional[str]
112 - timerange_start: Optional[str]
113 - timestamp: str
114 - timestamp_processing: str
115 -
116 -
117 -class AlertEvent(BaseModel):
118 - event: Event
119 - index_name: str
120 - index_type: str
121 -
122 -
123 -class Filter(BaseModel):
124 - alerts: str
125 - event_definitions: List[str]
126 -
127 -
128 -class Timerange(BaseModel):
129 - range: int
130 - type: str
131 -
132 -
133 -class Parameters(BaseModel):
134 - page: int
135 - per_page: int
136 - query: str
137 - sort_by: str
138 - sort_direction: str
139 - timerange: Timerange
140 - filter: Filter
141 -
142 -
143 -class Alerts(BaseModel):
144 - context: Context
145 - duration: int
146 - events: List[AlertEvent]
147 - parameters: Parameters
148 - total_events: int
149 - used_indices: List[str]
150 -
151 -
152 -class GraylogAlertsResponse(BaseModel):
153 - alerts: Alerts
154 - message: str
155 - success: bool
backend/app/connectors/graylog/schema/management.py deleted
-46
@@ -1,46 +0,0 @@
1 -from pydantic import BaseModel
2 -
3 -
4 -class DeletedIndexBody(BaseModel):
5 - index_name: str
6 -
7 -
8 -class DeletedIndexResponse(BaseModel):
9 - success: bool
10 - message: str
11 -
12 -
13 -class StopInputBody(BaseModel):
14 - input_id: str
15 -
16 -
17 -class StopInputResponse(BaseModel):
18 - success: bool
19 - message: str
20 -
21 -
22 -class StartInputBody(BaseModel):
23 - input_id: str
24 -
25 -
26 -class StartInputResponse(BaseModel):
27 - success: bool
28 - message: str
29 -
30 -
31 -class StopStreamBody(BaseModel):
32 - stream_id: str
33 -
34 -
35 -class StopStreamResponse(BaseModel):
36 - success: bool
37 - message: str
38 -
39 -
40 -class StartStreamBody(BaseModel):
41 - stream_id: str
42 -
43 -
44 -class StartStreamResponse(BaseModel):
45 - success: bool
46 - message: str
backend/app/connectors/graylog/schema/monitoring.py deleted
-53
@@ -1,53 +0,0 @@
1 -from typing import List
2 -from typing import Optional
3 -
4 -from pydantic import BaseModel
5 -from pydantic import Field
6 -
7 -
8 -class GraylogMessages(BaseModel):
9 - caller: str
10 - content: str
11 - node_id: str
12 - timestamp: str
13 -
14 -
15 -class GraylogTotalMessages(BaseModel):
16 - total: int
17 -
18 -
19 -class GraylogMessagesResponse(BaseModel):
20 - graylog_messages: List[GraylogMessages]
21 - success: bool
22 - message: str
23 - total_messages: int
24 -
25 -
26 -class GraylogThroughputMetrics(BaseModel):
27 - metric: str
28 - value: float
29 -
30 -
31 -class GraylogThroughputMetricsCollection(BaseModel):
32 - graylog2_buffers_input_usage: Optional[str] = Field(alias="org.graylog2.buffers.input.usage")
33 - graylog2_buffers_output_usage: Optional[str] = Field(alias="org.graylog2.buffers.output.usage")
34 - graylog2_buffers_process_usage: Optional[str] = Field(alias="org.graylog2.buffers.process.usage")
35 - graylog2_throughput_input_1_sec_rate: Optional[str] = Field(alias="org.graylog2.throughput.input.1-sec-rate")
36 - graylog2_throughput_output_1_sec_rate: Optional[str] = Field(alias="org.graylog2.throughput.output.1-sec-rate")
37 - graylog2_throughput_output: Optional[str] = Field(alias="org.graylog2.throughput.output")
38 - graylog2_throughput_input: Optional[str] = Field(alias="org.graylog2.throughput.input")
39 -
40 -
41 -class GraylogThroughputMetricsList(BaseModel):
42 - throughput_metrics: List[GraylogThroughputMetrics]
43 -
44 -
45 -class GraylogUncommittedJournalEntries(BaseModel):
46 - uncommitted_journal_entries: int
47 -
48 -
49 -class GraylogMetricsResponse(BaseModel):
50 - throughput_metrics: List[GraylogThroughputMetrics]
51 - uncommitted_journal_entries: int
52 - message: str
53 - success: bool
backend/app/connectors/graylog/schema/pipelines.py deleted
-44
@@ -1,44 +0,0 @@
1 -from typing import List
2 -from typing import Optional
3 -
4 -from pydantic import BaseModel
5 -
6 -
7 -class Stage(BaseModel):
8 - match: str
9 - rules: List[str]
10 - stage: int
11 -
12 -
13 -class Pipeline(BaseModel):
14 - created_at: str
15 - description: str
16 - errors: Optional[None]
17 - id: str
18 - modified_at: str
19 - source: str
20 - stages: List[Stage]
21 - title: str
22 -
23 -
24 -class GraylogPipelinesResponse(BaseModel):
25 - message: str
26 - pipelines: List[Pipeline]
27 - success: bool
28 -
29 -
30 -class PipelineRule(BaseModel):
31 - created_at: str
32 - description: str
33 - errors: Optional[None]
34 - id: str
35 - modified_at: str
36 - source: str
37 - title: str
38 -
39 -
40 -# Define the main response model
41 -class PipelineRulesResponse(BaseModel):
42 - message: str
43 - pipeline_rules: List[PipelineRule]
44 - success: bool
backend/app/connectors/graylog/schema/streams.py deleted
-38
@@ -1,38 +0,0 @@
1 -from typing import List
2 -from typing import Optional
3 -
4 -from pydantic import BaseModel
5 -
6 -
7 -class Rule(BaseModel):
8 - description: Optional[str]
9 - field: str
10 - id: str
11 - inverted: bool
12 - stream_id: str
13 - type: int
14 - value: str
15 -
16 -
17 -class Stream(BaseModel):
18 - content_pack: Optional[str]
19 - created_at: str
20 - creator_user_id: str
21 - description: str
22 - disabled: bool
23 - id: str
24 - index_set_id: str
25 - is_default: bool
26 - is_editable: bool
27 - matching_type: str
28 - outputs: list
29 - remove_matches_from_default_stream: bool
30 - rules: List[Rule]
31 - title: str
32 -
33 -
34 -class GraylogStreamsResponse(BaseModel):
35 - message: str
36 - streams: List[Stream]
37 - total: int
38 - success: bool
backend/app/connectors/graylog/services/collector.py deleted
-126
@@ -1,126 +0,0 @@
1 -from typing import List
2 -from typing import Tuple
3 -
4 -from loguru import logger
5 -
6 -from app.connectors.graylog.schema.collector import ConfiguredInput
7 -from app.connectors.graylog.schema.collector import ConfiguredInputsResponse
8 -from app.connectors.graylog.schema.collector import GraylogIndexItem
9 -from app.connectors.graylog.schema.collector import GraylogIndicesResponse
10 -from app.connectors.graylog.schema.collector import GraylogInputsResponse
11 -from app.connectors.graylog.schema.collector import RunningInput
12 -from app.connectors.graylog.schema.collector import RunningInputsResponse
13 -from app.connectors.graylog.utils.universal import send_get_request
14 -
15 -
16 -def get_indices_full() -> GraylogIndicesResponse:
17 - """Get indices from Graylog."""
18 - logger.info("Getting indices from Graylog")
19 - indices_collected = send_get_request(endpoint="/api/system/indexer/indices")
20 - if indices_collected["success"]:
21 - indices_data = indices_collected["data"]["all"]["indices"]
22 -
23 - # Convert the dictionary to a list of GraylogIndexItem
24 - indices_list = [GraylogIndexItem(index_name=name, index_info=info) for name, info in indices_data.items()]
25 -
26 - return GraylogIndicesResponse(indices=indices_list, success=True, message="Indices collected successfully")
27 - else:
28 - return GraylogIndicesResponse(indices=[], success=False, message="Failed to collect indices")
29 -
30 -
31 -def fetch_configured_inputs() -> Tuple[bool, List[ConfiguredInput]]:
32 - configured_inputs_collected = send_get_request(endpoint="/api/system/inputs")
33 - success = configured_inputs_collected.get("success", False)
34 -
35 - if success:
36 - return True, [ConfiguredInput(**input_data) for input_data in configured_inputs_collected["data"]["inputs"]]
37 - else:
38 - logger.error("Failed to fetch configured inputs")
39 - return False, []
40 -
41 -
42 -def fetch_running_inputs() -> Tuple[bool, List[RunningInput]]:
43 - running_inputs_collected = send_get_request(endpoint="/api/system/inputstates")
44 - success = running_inputs_collected.get("success", False)
45 -
46 - if success:
47 - return True, [RunningInput(**input_data) for input_data in running_inputs_collected["data"]["states"]]
48 - else:
49 - logger.error("Failed to fetch running inputs")
50 - return False, []
51 -
52 -
53 -def get_inputs() -> GraylogInputsResponse:
54 - """Get inputs from Graylog."""
55 - logger.info("Getting inputs from Graylog")
56 -
57 - config_success, configured_inputs_list = fetch_configured_inputs()
58 - run_success, running_inputs_list = fetch_running_inputs()
59 -
60 - if config_success and run_success:
61 - logger.info("Successfully fetched both configured and running inputs")
62 - return GraylogInputsResponse(
63 - configured_inputs=configured_inputs_list,
64 - running_inputs=running_inputs_list,
65 - success=True,
66 - message="Successfully retrieved inputs",
67 - )
68 - else:
69 - logger.error("Failed to fetch one or both types of inputs")
70 - return GraylogInputsResponse(configured_inputs=[], running_inputs=[], success=False, message="Failed to collect inputs")
71 -
72 -
73 -def get_inputs_running() -> RunningInputsResponse:
74 - """Get running inputs from Graylog."""
75 - logger.info("Getting running inputs from Graylog")
76 - run_success, running_inputs_list = fetch_running_inputs()
77 - if run_success:
78 - return RunningInputsResponse(running_inputs=running_inputs_list, success=True, message="Successfully retrieved running inputs")
79 -
80 -
81 -def get_inputs_configured() -> ConfiguredInputsResponse:
82 - """Get configured inputs from Graylog."""
83 - logger.info("Getting configured inputs from Graylog")
84 - config_success, configured_inputs_list = fetch_configured_inputs()
85 - if config_success:
86 - return ConfiguredInputsResponse(
87 - configured_inputs=configured_inputs_list,
88 - success=True,
89 - message="Successfully retrieved configured inputs",
90 - )
91 -
92 -
93 -def get_index_names() -> List[str]:
94 - """
95 - Gets the names of all the indices in Graylog.
96 -
97 - Returns:
98 - List[str]: A list of all the index names.
99 - """
100 - logger.info("Getting index names from Graylog")
101 -
102 - indices_collected = get_indices_full()
103 -
104 - if indices_collected.success:
105 - # Access the index_name attribute directly
106 - return [index.index_name for index in indices_collected.indices]
107 - else:
108 - return []
109 -
110 -
111 -def get_input_ids() -> List[str]:
112 - """
113 - Gets the IDs of all the inputs in Graylog.
114 -
115 - Returns:
116 - List[str]: A list of all the input IDs.
117 - """
118 - logger.info("Getting input IDs from Graylog")
119 -
120 - success, inputs_collected = fetch_configured_inputs()
121 -
122 - if success:
123 - # Access the input_id attribute directly
124 - return [input.id for input in inputs_collected]
125 - else:
126 - return []
backend/app/connectors/graylog/services/events.py deleted
-59
@@ -1,59 +0,0 @@
1 -from loguru import logger
2 -
3 -from app.connectors.graylog.schema.events import AlertEvent
4 -from app.connectors.graylog.schema.events import AlertQuery
5 -from app.connectors.graylog.schema.events import Alerts
6 -from app.connectors.graylog.schema.events import Context
7 -from app.connectors.graylog.schema.events import EventDefinition
8 -from app.connectors.graylog.schema.events import GraylogAlertsResponse
9 -from app.connectors.graylog.schema.events import GraylogEventDefinitionsResponse
10 -from app.connectors.graylog.schema.events import Parameters
11 -from app.connectors.graylog.utils.universal import send_get_request
12 -from app.connectors.graylog.utils.universal import send_post_request
13 -
14 -
15 -def get_event_definitions() -> GraylogEventDefinitionsResponse:
16 - """Get event definitions from Graylog."""
17 - logger.info("Getting event definitions from Graylog")
18 - event_definitions_collected = send_get_request(endpoint="/api/events/definitions")
19 - if event_definitions_collected["success"]:
20 - event_definitions_data = event_definitions_collected["data"]["event_definitions"]
21 -
22 - # Convert the dictionary to a list of GraylogIndexItem
23 - event_definitions_list = [EventDefinition(**event_definition_data) for event_definition_data in event_definitions_data]
24 -
25 - return GraylogEventDefinitionsResponse(
26 - event_definitions=event_definitions_list,
27 - success=True,
28 - message="Event definitions collected successfully",
29 - )
30 - else:
31 - return GraylogEventDefinitionsResponse(event_definitions=[], success=False, message="Failed to collect event definitions")
32 -
33 -
34 -def get_alerts(alert_query: AlertQuery) -> GraylogAlertsResponse:
35 - logger.info("Getting alerts from Graylog")
36 - response = send_post_request(endpoint="/api/events/search", data=alert_query.dict())
37 -
38 - if response["success"]:
39 - raw_alerts_data = response["data"]
40 - # Convert raw event data to Event objects
41 - event_objects = [AlertEvent(**event_data) for event_data in raw_alerts_data["events"]]
42 -
43 - # Build the Alerts object
44 - alerts = Alerts(
45 - context=Context(**raw_alerts_data["context"]),
46 - duration=raw_alerts_data["duration"],
47 - events=event_objects,
48 - parameters=Parameters(**raw_alerts_data["parameters"]),
49 - total_events=raw_alerts_data["total_events"],
50 - used_indices=raw_alerts_data["used_indices"],
51 - )
52 -
53 - # Build the final GraylogAlertsResponse
54 - final_response = GraylogAlertsResponse(alerts=alerts, message="Successfully collected alerts", success=True)
55 -
56 - logger.info(f"Events collected: {event_objects}")
57 - return final_response
58 - else:
59 - return GraylogAlertsResponse(alerts=Alerts(events=[]), success=False, message="Failed to collect alerts")
backend/app/connectors/graylog/services/management.py deleted
-73
@@ -1,73 +0,0 @@
1 -from loguru import logger
2 -
3 -from app.connectors.graylog.schema.management import DeletedIndexBody
4 -from app.connectors.graylog.schema.management import DeletedIndexResponse
5 -from app.connectors.graylog.schema.management import StartInputBody
6 -from app.connectors.graylog.schema.management import StartInputResponse
7 -from app.connectors.graylog.schema.management import StartStreamBody
8 -from app.connectors.graylog.schema.management import StartStreamResponse
9 -from app.connectors.graylog.schema.management import StopInputBody
10 -from app.connectors.graylog.schema.management import StopInputResponse
11 -from app.connectors.graylog.schema.management import StopStreamBody
12 -from app.connectors.graylog.schema.management import StopStreamResponse
13 -from app.connectors.graylog.services.collector import get_index_names
14 -from app.connectors.graylog.utils.universal import send_delete_request
15 -from app.connectors.graylog.utils.universal import send_post_request
16 -from app.connectors.graylog.utils.universal import send_put_request
17 -
18 -
19 -def delete_index(index_name: DeletedIndexBody) -> DeletedIndexResponse:
20 - """Delete an index from Graylog."""
21 - logger.info(f"Deleting index {index_name} from Graylog")
22 - send_delete_request(endpoint=f"/api/system/indexer/indices/{index_name}")
23 - # Check if the index still exists
24 - index_names = get_index_names()
25 - logger.info(f"Index names: {index_names}")
26 - if index_name in index_names:
27 - return DeletedIndexResponse(
28 - success=False,
29 - message=f"Failed to delete index {index_name}. If the index is still in use, it cannot be deleted.",
30 - )
31 - else:
32 - return DeletedIndexResponse(success=True, message=f"Successfully deleted index {index_name}")
33 -
34 -
35 -def stop_input(input_id: StopInputBody) -> StopInputResponse:
36 - """Stop an input in Graylog."""
37 - logger.info(f"Stopping input {input_id} in Graylog")
38 - response = send_delete_request(endpoint=f"/api/system/inputstates/{input_id}")
39 - if response["success"]:
40 - return StopInputResponse(success=True, message=f"Successfully stopped input {input_id}")
41 - else:
42 - return StopInputResponse(success=False, message=f"Failed to stop input {input_id}")
43 -
44 -
45 -def start_input(input_id: StartInputBody) -> StartInputResponse:
46 - """Start an input in Graylog."""
47 - logger.info(f"Starting input {input_id} in Graylog")
48 - response = send_put_request(endpoint=f"/api/system/inputstates/{input_id}")
49 - if response["success"]:
50 - return StartInputResponse(success=True, message=f"Successfully started input {input_id}")
51 - else:
52 - return StartInputResponse(success=False, message=f"Failed to start input {input_id}")
53 -
54 -
55 -def stop_stream(stream_id: StopStreamBody) -> StopStreamResponse:
56 - """Stop a stream in Graylog."""
57 - logger.info(f"Stopping stream {stream_id} in Graylog")
58 - response = send_post_request(endpoint=f"/api/streams/{stream_id}/pause")
59 - logger.info(f"Response: {response}")
60 - if response["success"]:
61 - return StopStreamResponse(success=True, message=f"Successfully stopped stream {stream_id}")
62 - else:
63 - return StopStreamResponse(success=False, message=f"Failed to stop stream {stream_id}")
64 -
65 -
66 -def start_stream(stream_id: StartStreamBody) -> StartStreamResponse:
67 - """Start a stream in Graylog."""
68 - logger.info(f"Starting stream {stream_id} in Graylog")
69 - response = send_post_request(endpoint=f"/api/streams/{stream_id}/resume")
70 - if response["success"]:
71 - return StartStreamResponse(success=True, message=f"Successfully started stream {stream_id}")
72 - else:
73 - return StartStreamResponse(success=False, message=f"Failed to start stream {stream_id}")
backend/app/connectors/graylog/services/monitoring.py deleted
-86
@@ -1,86 +0,0 @@
1 -from loguru import logger
2 -
3 -from app.connectors.graylog.schema.monitoring import GraylogMessages
4 -from app.connectors.graylog.schema.monitoring import GraylogMessagesResponse
5 -from app.connectors.graylog.schema.monitoring import GraylogMetricsResponse
6 -from app.connectors.graylog.schema.monitoring import GraylogThroughputMetrics
7 -from app.connectors.graylog.schema.monitoring import GraylogThroughputMetricsCollection
8 -from app.connectors.graylog.schema.monitoring import GraylogUncommittedJournalEntries
9 -from app.connectors.graylog.utils.universal import send_get_request
10 -
11 -
12 -def get_messages(page_number: int) -> GraylogMessagesResponse:
13 - """Get messages from Graylog."""
14 - logger.info("Getting messages from Graylog")
15 - params = {"page": page_number}
16 - messages_collected = send_get_request(endpoint="/api/system/messages", params=params)
17 - if messages_collected["success"]:
18 - graylog_messages_list = []
19 - for message in messages_collected["data"]["messages"]:
20 - graylog_message = GraylogMessages(
21 - caller=message["caller"],
22 - content=message["content"],
23 - node_id=message["node_id"],
24 - timestamp=message["timestamp"],
25 - )
26 - graylog_messages_list.append(graylog_message)
27 - return GraylogMessagesResponse(
28 - graylog_messages=graylog_messages_list,
29 - success=True,
30 - message="Messages collected successfully",
31 - total_messages=messages_collected["data"]["total"],
32 - )
33 - else:
34 - return GraylogMessagesResponse(graylog_messages=[], success=False, message="Failed to collect messages")
35 -
36 -
37 -def fetch_metrics_from_graylog() -> dict:
38 - return send_get_request(endpoint="/api/system/metrics")
39 -
40 -
41 -def fetch_uncommitted_journal_entries() -> dict:
42 - return send_get_request(endpoint="/api/system/journal")
43 -
44 -
45 -def merge_metrics_data(throughput_metrics_collected: dict) -> dict:
46 - throughput_metrics = throughput_metrics_collected["data"]["gauges"]
47 - input_output_metrics = throughput_metrics_collected["data"]["counters"]
48 - return {**throughput_metrics, **input_output_metrics}
49 -
50 -
51 -def filter_and_create_throughput_metrics(merged_metrics: dict) -> list:
52 - model_fields = [field_info.alias for field_info in GraylogThroughputMetricsCollection.__fields__.values()]
53 - throughput_metrics_list = [
54 - GraylogThroughputMetrics(metric=metric_name, value=metric_data.get("value", 0))
55 - for metric_name, metric_data in merged_metrics.items()
56 - if metric_name in model_fields
57 - ]
58 - return throughput_metrics_list
59 -
60 -
61 -def get_metrics() -> GraylogMetricsResponse:
62 - logger.info("Getting metrics from Graylog")
63 - throughput_metrics_collected = fetch_metrics_from_graylog()
64 - uncommitted_journal_entries_collected = fetch_uncommitted_journal_entries()
65 -
66 - if throughput_metrics_collected["success"] and uncommitted_journal_entries_collected["success"]:
67 - merged_metrics = merge_metrics_data(throughput_metrics_collected)
68 - throughput_metrics_list = filter_and_create_throughput_metrics(merged_metrics)
69 -
70 - uncommitted_journal_entries = GraylogUncommittedJournalEntries(
71 - uncommitted_journal_entries=uncommitted_journal_entries_collected["data"]["uncommitted_journal_entries"],
72 - )
73 -
74 - return GraylogMetricsResponse(
75 - throughput_metrics=throughput_metrics_list,
76 - uncommitted_journal_entries=uncommitted_journal_entries.uncommitted_journal_entries,
77 - success=True,
78 - message="Metrics collected successfully",
79 - )
80 - else:
81 - return GraylogMetricsResponse(
82 - throughput_metrics=[],
83 - uncommitted_journal_entries=0,
84 - success=False,
85 - message="Failed to collect metrics",
86 - )
backend/app/connectors/graylog/services/pipelines.py deleted
-29
@@ -1,29 +0,0 @@
1 -from loguru import logger
2 -
3 -from app.connectors.graylog.schema.pipelines import GraylogPipelinesResponse
4 -from app.connectors.graylog.schema.pipelines import Pipeline
5 -from app.connectors.graylog.schema.pipelines import PipelineRule
6 -from app.connectors.graylog.schema.pipelines import PipelineRulesResponse
7 -from app.connectors.graylog.utils.universal import send_get_request
8 -
9 -
10 -def get_pipelines() -> GraylogPipelinesResponse:
11 - """Get pipelines from Graylog."""
12 - logger.info("Getting pipelines from Graylog")
13 - pipelines_collected = send_get_request(endpoint="/api/system/pipelines/pipeline")
14 - if pipelines_collected["success"]:
15 - pipelines_list = [Pipeline(**pipeline_data) for pipeline_data in pipelines_collected["data"]]
16 - return GraylogPipelinesResponse(pipelines=pipelines_list, success=True, message="Pipelines collected successfully")
17 - else:
18 - return GraylogPipelinesResponse(pipelines=[], success=False, message="Failed to collect pipelines")
19 -
20 -
21 -def get_pipeline_rules() -> PipelineRulesResponse:
22 - """Get pipeline rules from Graylog."""
23 - logger.info("Getting pipeline rules from Graylog")
24 - pipeline_rules_collected = send_get_request(endpoint="/api/system/pipelines/rule")
25 - if pipeline_rules_collected["success"]:
26 - pipeline_rules_list = [PipelineRule(**pipeline_rule_data) for pipeline_rule_data in pipeline_rules_collected["data"]]
27 - return PipelineRulesResponse(pipeline_rules=pipeline_rules_list, success=True, message="Pipeline rules collected successfully")
28 - else:
29 - return PipelineRulesResponse(pipeline_rules=[], success=False, message="Failed to collect pipeline rules")
backend/app/connectors/graylog/services/streams.py deleted
-33
@@ -1,33 +0,0 @@
1 -from typing import List
2 -
3 -from loguru import logger
4 -
5 -from app.connectors.graylog.schema.streams import GraylogStreamsResponse
6 -from app.connectors.graylog.schema.streams import Stream
7 -from app.connectors.graylog.utils.universal import send_get_request
8 -
9 -
10 -def get_streams() -> GraylogStreamsResponse:
11 - """Get streams from Graylog."""
12 - logger.info("Getting streams from Graylog")
13 - streams_collected = send_get_request(endpoint="/api/streams")
14 - if streams_collected["success"]:
15 - streams_list = [Stream(**stream_data) for stream_data in streams_collected["data"]["streams"]]
16 - return GraylogStreamsResponse(
17 - streams=streams_list,
18 - success=True,
19 - message="Streams collected successfully",
20 - total=streams_collected["data"]["total"],
21 - )
22 - else:
23 - return GraylogStreamsResponse(streams=[], success=False, message="Failed to collect streams", total=0)
24 -
25 -
26 -def get_stream_ids() -> List[str]:
27 - """Get stream IDs from Graylog."""
28 - logger.info("Getting stream IDs from Graylog")
29 - streams_collected = send_get_request(endpoint="/api/streams")
30 - if streams_collected["success"]:
31 - return [stream_data["id"] for stream_data in streams_collected["data"]["streams"]]
32 - else:
33 - return []
backend/app/connectors/graylog/utils/universal.py deleted
-207
@@ -1,207 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import Optional
4 -
5 -import requests
6 -from loguru import logger
7 -
8 -from app.connectors.utils import get_connector_info_from_db
9 -
10 -HEADERS = {"X-Requested-By": "CoPilot"}
11 -
12 -
13 -def verify_graylog_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
14 - """
15 - Verifies the connection to Graylog service.
16 -
17 - Returns:
18 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
19 - """
20 - logger.info(
21 - f"Verifying the graylog connection to {attributes['connector_url']}",
22 - )
23 - try:
24 - graylog_roles = requests.get(
25 - f"{attributes['connector_url']}/api/authz/roles/user/{attributes['connector_username']}",
26 - auth=(
27 - attributes["connector_username"],
28 - attributes["connector_password"],
29 - ),
30 - verify=False,
31 - )
32 - if graylog_roles.status_code == 200:
33 - logger.info(
34 - f"Connection to {attributes['connector_url']} successful",
35 - )
36 - return {"connectionSuccessful": True, "message": "Graylog connection successful"}
37 - else:
38 - logger.error(
39 - f"Connection to {attributes['connector_url']} failed with error: {graylog_roles.text}",
40 - )
41 - return {
42 - "connectionSuccessful": False,
43 - "message": f"Connection to {attributes['connector_url']} failed with error: {graylog_roles.text}",
44 - }
45 - except Exception as e:
46 - logger.error(
47 - f"Connection to {attributes['connector_url']} failed with error: {e}",
48 - )
49 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error: {e}"}
50 -
51 -
52 -def verify_graylog_connection(connector_name: str) -> str:
53 - """
54 - Returns if connection to Graylog service is successful.
55 - """
56 - logger.info("Getting Graylog authentication token")
57 - attributes = get_connector_info_from_db(connector_name)
58 - if attributes is None:
59 - logger.error("No Graylog connector found in the database")
60 - return None
61 - return verify_graylog_credentials(attributes)
62 -
63 -
64 -def send_get_request(endpoint: str, params: Optional[Dict[str, Any]] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
65 - """
66 - Sends a GET request to the Graylog service.
67 -
68 - Args:
69 - endpoint (str): The endpoint to send the GET request to.
70 - params (Optional[Dict[str, Any]], optional): The parameters to send with the GET request. Defaults to None.
71 - connector_name (str, optional): The name of the connector to use. Defaults to "Graylogr".
72 -
73 - Returns:
74 - Dict[str, Any]: The response from the GET request.
75 - """
76 - logger.info(f"Sending GET request to {endpoint}")
77 - attributes = get_connector_info_from_db(connector_name)
78 - if attributes is None:
79 - logger.error("No Graylog connector found in the database")
80 - return None
81 - try:
82 - response = requests.get(
83 - f"{attributes['connector_url']}{endpoint}",
84 - headers=HEADERS,
85 - auth=(
86 - attributes["connector_username"],
87 - attributes["connector_password"],
88 - ),
89 - params=params,
90 - verify=False,
91 - )
92 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
93 - except Exception as e:
94 - logger.error(f"Failed to send GET request to {endpoint} with error: {e}")
95 - return {"success": False, "message": f"Failed to send GET request to {endpoint} with error: {e}"}
96 -
97 -
98 -def send_post_request(endpoint: str, data: Dict[str, Any] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
99 - """
100 - Sends a POST request to the Graylog service.
101 -
102 - Args:
103 - endpoint (str): The endpoint to send the POST request to.
104 - data (Dict[str, Any]): The data to send with the POST request.
105 - connector_name (str, optional): The name of the connector to use. Defaults to "Graylog".
106 -
107 - Returns:
108 - Dict[str, Any]: The response from the POST request.
109 - """
110 - logger.info(f"Sending POST request to {endpoint}")
111 - attributes = get_connector_info_from_db(connector_name)
112 - if attributes is None:
113 - logger.error("No Graylog connector found in the database")
114 - return {"success": False, "message": "No Graylog connector found in the database"}
115 -
116 - try:
117 - response = requests.post(
118 - f"{attributes['connector_url']}{endpoint}",
119 - headers=HEADERS,
120 - auth=(
121 - attributes["connector_username"],
122 - attributes["connector_password"],
123 - ),
124 - json=data,
125 - verify=False,
126 - )
127 -
128 - if response.status_code == 204:
129 - return {"data": None, "success": True, "message": "Successfully completed request with no content"}
130 - else:
131 - return {
132 - "data": response.json(),
133 - "success": False if response.status_code >= 400 else True,
134 - "message": "Successfully retrieved data" if response.status_code < 400 else "Failed to retrieve data",
135 - }
136 - except Exception as e:
137 - logger.debug(f"Response: {response}")
138 - logger.error(f"Failed to send POST request to {endpoint} with error: {e}")
139 - return {"success": False, "message": f"Failed to send POST request to {endpoint} with error: {e}"}
140 -
141 -
142 -def send_delete_request(endpoint: str, params: Optional[Dict[str, Any]] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
143 - """
144 - Sends a DELETE request to the Graylog service.
145 -
146 - Args:
147 - endpoint (str): The endpoint to send the DELETE request to.
148 - params (Optional[Dict[str, Any]], optional): The parameters to send with the DELETE request. Defaults to None.
149 - connector_name (str, optional): The name of the connector to use. Defaults to "Graylog".
150 -
151 - Returns:
152 - Dict[str, Any]: The response from the DELETE request.
153 - """
154 - logger.info(f"Sending DELETE request to {endpoint}")
155 - attributes = get_connector_info_from_db(connector_name)
156 - if attributes is None:
157 - logger.error("No Graylog connector found in the database")
158 - return None
159 - try:
160 - response = requests.delete(
161 - f"{attributes['connector_url']}{endpoint}",
162 - headers=HEADERS,
163 - auth=(
164 - attributes["connector_username"],
165 - attributes["connector_password"],
166 - ),
167 - params=params,
168 - verify=False,
169 - )
170 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
171 - except Exception as e:
172 - logger.error(f"Failed to send DELETE request to {endpoint} with error: {e}")
173 - return {"success": False, "message": f"Failed to send DELETE request to {endpoint} with error: {e}"}
174 -
175 -
176 -def send_put_request(endpoint: str, data: Optional[Dict[str, Any]] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
177 - """
178 - Sends a PUT request to the Graylog service.
179 -
180 - Args:
181 - endpoint (str): The endpoint to send the PUT request to.
182 - data (Optional[Dict[str, Any]]): The data to send with the PUT request.
183 - connector_name (str, optional): The name of the connector to use. Defaults to "Graylog".
184 -
185 - Returns:
186 - Dict[str, Any]: The response from the PUT request.
187 - """
188 - logger.info(f"Sending PUT request to {endpoint}")
189 - attributes = get_connector_info_from_db(connector_name)
190 - if attributes is None:
191 - logger.error("No Graylog connector found in the database")
192 - return None
193 - try:
194 - response = requests.put(
195 - f"{attributes['connector_url']}{endpoint}",
196 - headers=HEADERS,
197 - auth=(
198 - attributes["connector_username"],
199 - attributes["connector_password"],
200 - ),
201 - json=data,
202 - verify=False,
203 - )
204 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
205 - except Exception as e:
206 - logger.error(f"Failed to send PUT request to {endpoint} with error: {e}")
207 - return {"success": False, "message": f"Failed to send PUT request to {endpoint} with error: {e}"}
backend/app/connectors/models.py deleted
-95
@@ -1,95 +0,0 @@
1 -from datetime import datetime
2 -from typing import List
3 -from typing import Optional
4 -
5 -from sqlmodel import Field
6 -from sqlmodel import Relationship
7 -from sqlmodel import SQLModel
8 -
9 -
10 -class ConnectorHistory(SQLModel, table=True):
11 - """
12 - Model representing the history logs of each connector.
13 -
14 - :ivar id: Unique integer ID of the history log.
15 - :ivar connector_id: Foreign key linking to the Connectors table.
16 - :ivar change_timestamp: Timestamp when the change was made.
17 - :ivar change_description: Description of the change.
18 - :ivar connector: Relationship to the Connectors model.
19 - """
20 -
21 - id: Optional[int] = Field(default=None, primary_key=True)
22 - connector_id: int = Field(foreign_key="connectors.id")
23 - change_timestamp: datetime = Field(default=datetime.utcnow())
24 - change_description: str
25 -
26 - # Relationship
27 - connector: Optional["Connectors"] = Relationship(back_populates="history_logs")
28 -
29 -
30 -class Connectors(SQLModel, table=True):
31 - """
32 - Model representing each connector and its attributes.
33 -
34 - :ivar id: Unique integer ID of the connector.
35 - :ivar connector_name: Name of the connector.
36 - :ivar connector_type: Type or version of the connector.
37 - :ivar connector_url: URL endpoint of the connector.
38 - :ivar connector_last_updated: Timestamp when the connector was last updated.
39 - :ivar connector_username: Optional username for the connector.
40 - :ivar connector_password: Optional password for the connector.
41 - :ivar connector_api_key: Optional API key for the connector.
42 - :ivar connector_description: Description of what the connector does.
43 - :ivar connector_supports: Information on what the connector supports.
44 - :ivar connector_configured: Boolean indicating if the connector is configured.
45 - :ivar connector_verified: Boolean indicating if the connector is verified.
46 - :ivar connector_accepts_api_key: Boolean indicating if the connector accepts API keys.
47 - :ivar connector_accepts_username_password: Boolean indicating if the connector accepts username and password.
48 - :ivar connector_accepts_file: Boolean indicating if the connector accepts files.
49 - :ivar history_logs: Relationship to the ConnectorHistory model.
50 - """
51 -
52 - id: Optional[int] = Field(default=None, primary_key=True)
53 - connector_name: str = Field()
54 - connector_type: str = Field()
55 - connector_url: str = Field()
56 - connector_last_updated: datetime = Field(default=datetime.utcnow())
57 - connector_username: Optional[str] = Field(default=None)
58 - connector_password: Optional[str] = Field(default=None)
59 - connector_api_key: Optional[str] = Field(default=None)
60 -
61 - # Fields moved from ConnectorsAvailable
62 - connector_description: Optional[str] = Field(default=None)
63 - connector_supports: Optional[str] = Field(default=None)
64 - connector_configured: bool = Field(default=False)
65 - connector_verified: bool = Field(default=False)
66 - connector_accepts_api_key: bool = Field(default=False)
67 - connector_accepts_username_password: bool = Field(default=False)
68 - connector_accepts_file: bool = Field(default=False)
69 -
70 - # Relationship
71 - history_logs: List[ConnectorHistory] = Relationship(back_populates="connector")
72 -
73 -
74 -# Example usage
75 -# new_connector = Connectors(
76 -# connector_name="Wazuh-Indexer",
77 -# connector_type="4.4.1",
78 -# connector_url="https://ashwix01.socfortress.local:9200",
79 -# connector_username="admin",
80 -# connector_password="password_here",
81 -# connector_api_key="api_key_here",
82 -# # Fields from ConnectorsAvailable
83 -# connector_description="Description here",
84 -# connector_supports="Supports list here",
85 -# connector_configured=True,
86 -# connector_verified=True,
87 -# connector_accepts_api_key=True,
88 -# connector_accepts_username_password=True,
89 -# connector_accepts_file=False
90 -# )
91 -
92 -# new_log = ConnectorHistory(
93 -# connector_id=1, # This should be the ID of the corresponding connector
94 -# change_description="Changed the API key."
95 -# )
backend/app/connectors/routes.py deleted
-159
@@ -1,159 +0,0 @@
1 -from typing import Union
2 -
3 -## Auth Things
4 -from fastapi import APIRouter
5 -from fastapi import File
6 -from fastapi import HTTPException
7 -from fastapi import Security
8 -from fastapi import UploadFile
9 -from loguru import logger
10 -
11 -from app.auth.utils import AuthHandler
12 -from app.connectors.schema import ConnectorListResponse
13 -from app.connectors.schema import ConnectorResponse
14 -from app.connectors.schema import ConnectorsListResponse
15 -from app.connectors.schema import UpdateConnector
16 -from app.connectors.schema import VerifyConnectorResponse
17 -from app.connectors.services import ConnectorServices
18 -
19 -connector_router = APIRouter()
20 -
21 -
22 -@connector_router.get(
23 - "",
24 - response_model=ConnectorsListResponse,
25 - description="Fetch all available connectors",
26 - dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
27 -)
28 -async def get_connectors() -> ConnectorListResponse:
29 - """
30 - Fetch all available connectors from the database.
31 -
32 - This endpoint retrieves all the connectors stored in the database and returns them
33 - along with a success status and message.
34 -
35 - Returns:
36 - ConnectorListResponse: A Pydantic model containing a list of connectors and additional metadata.
37 -
38 - Raises:
39 - HTTPException: An exception with a 404 status code is raised if no connectors are found.
40 - """
41 -
42 - connectors = ConnectorServices.fetch_all_connectors()
43 - if connectors:
44 - return {"connectors": connectors, "success": True, "message": "Connectors fetched successfully"}
45 - else:
46 - raise HTTPException(status_code=404, detail="No connectors found")
47 -
48 -
49 -@connector_router.get(
50 - "/{connector_id}",
51 - response_model=ConnectorListResponse,
52 - description="Fetch a specific connector",
53 - dependencies=[Security(AuthHandler().require_any_scope("admin", "test"))],
54 -)
55 -async def get_connector(connector_id: int) -> Union[ConnectorResponse, HTTPException]:
56 - """
57 - Fetch a specific connector by its ID.
58 -
59 - This endpoint retrieves a connector identified by `connector_id` from the database.
60 -
61 - Args:
62 - connector_id (int): The unique identifier for the connector to fetch.
63 -
64 - Returns:
65 - ConnectorResponse: A Pydantic model representing the fetched connector.
66 -
67 - Raises:
68 - HTTPException: An exception with a 404 status code is raised if the connector is not found.
69 - """
70 - connector = ConnectorServices.fetch_connector_by_id(connector_id)
71 - if connector is not None:
72 - return {"connector": connector, "success": True, "message": "Connector fetched successfully"}
73 - else:
74 - raise HTTPException(status_code=404, detail=f"No connector found for ID: {connector_id}".format(connector_id=connector_id))
75 -
76 -
77 -@connector_router.post(
78 - "/verify/{connector_id}",
79 - response_model=VerifyConnectorResponse,
80 - description="Verify a connector. Makes an API call to the connector to verify it is working.",
81 -)
82 -async def verify_connector(connector_id: int) -> Union[VerifyConnectorResponse, HTTPException]:
83 - """
84 - Verify a connector by its ID.
85 -
86 - This endpoint verifies a connector identified by `connector_id` by making an API call to the connector.
87 -
88 - Args:
89 - connector_id (int): The unique identifier for the connector to verify.
90 -
91 - Returns:
92 - ConnectorResponse: A Pydantic model representing the verified connector.
93 -
94 - Raises:
95 - HTTPException: An exception with a 404 status code is raised if the connector is not found.
96 - """
97 - connector = ConnectorServices.verify_connector_by_id(connector_id)
98 - if connector is not None:
99 - logger.info(f"Connector verified successfully: {connector}")
100 - return connector
101 - else:
102 - raise HTTPException(status_code=404, detail=f"No connector found for ID: {connector_id}".format(connector_id=connector_id))
103 -
104 -
105 -@connector_router.put("/{connector_id}", response_model=ConnectorListResponse, description="Update a connector")
106 -async def update_connector(connector_id: int, connector: UpdateConnector) -> ConnectorListResponse:
107 - """
108 - Update a connector by its ID.
109 -
110 - This endpoint updates a connector identified by `connector_id` in the database.
111 -
112 - Args:
113 - connector_id (int): The unique identifier for the connector to update.
114 - connector (ConnectorListResponse): The updated connector data.
115 -
116 - Returns:
117 - ConnectorListResponse: A Pydantic model representing the updated connector.
118 -
119 - Raises:
120 - HTTPException: An exception with a 404 status code is raised if the connector is not found.
121 - """
122 - updated_connector = ConnectorServices.update_connector_by_id(connector_id, connector)
123 - if updated_connector is not None:
124 - return {"connector": updated_connector, "success": True, "message": "Connector updated successfully"}
125 - else:
126 - raise HTTPException(status_code=404, detail=f"No connector found for ID: {connector_id}".format(connector_id=connector_id))
127 -
128 -
129 -@connector_router.post("/upload/{connector_id}", description="Upload a YAML file for a specific connector")
130 -async def upload_yaml_file(connector_id: int, file: UploadFile = File(...)) -> dict:
131 - """
132 - Upload a YAML file for a specific connector ID.
133 -
134 - This endpoint allows you to upload a `.yaml` file for a specific connector
135 - identified by `connector_id`.
136 -
137 - Args:
138 - connector_id (int): The unique identifier for the connector.
139 - file (UploadFile): The `.yaml` file to be uploaded.
140 -
141 - Returns:
142 - dict: A dictionary with a success message and other information.
143 -
144 - Raises:
145 - HTTPException: An exception with a 400 status code is raised if the file format is incorrect or connector ID is not 6.
146 - """
147 - if connector_id != 6:
148 - raise HTTPException(status_code=400, detail="Only the Velociraptor connector is allowed for YAML file uploads.")
149 - if not file.filename.endswith(".yaml"):
150 - raise HTTPException(status_code=400, detail="Only .yaml files are allowed.")
151 - try:
152 - save_file_result = ConnectorServices.save_file(file)
153 - if save_file_result:
154 - return {"success": True, "message": "File uploaded successfully"}
155 - else:
156 - raise HTTPException(status_code=500, detail="Failed to upload file")
157 - except Exception as e:
158 - logger.error(f"Failed to upload file: {e}")
159 - raise HTTPException(status_code=500, detail="Failed to upload file")
backend/app/connectors/schema.py deleted
-61
@@ -1,61 +0,0 @@
1 -from datetime import datetime
2 -from typing import List
3 -from typing import Optional
4 -
5 -from pydantic import BaseModel
6 -
7 -
8 -class ConnectorHistoryResponse(BaseModel):
9 - id: Optional[int]
10 - connector_id: int
11 - change_timestamp: datetime
12 - change_description: str
13 -
14 - class Config:
15 - orm_mode = True
16 -
17 -
18 -class ConnectorResponse(BaseModel):
19 - id: Optional[int]
20 - connector_name: str
21 - connector_type: str
22 - connector_url: str
23 - connector_last_updated: datetime
24 - connector_username: Optional[str]
25 - connector_password: Optional[str]
26 - connector_api_key: Optional[str]
27 - connector_description: Optional[str]
28 - connector_supports: Optional[str]
29 - connector_configured: bool
30 - connector_verified: bool
31 - connector_accepts_api_key: bool
32 - connector_accepts_username_password: bool
33 - connector_accepts_file: bool
34 - history_logs: Optional[List[ConnectorHistoryResponse]]
35 -
36 - class Config:
37 - orm_mode = True
38 -
39 -
40 -class ConnectorsListResponse(BaseModel):
41 - connectors: List[ConnectorResponse]
42 - success: bool
43 - message: str
44 -
45 -
46 -class ConnectorListResponse(BaseModel):
47 - connector: ConnectorResponse
48 - success: bool
49 - message: str
50 -
51 -
52 -class VerifyConnectorResponse(BaseModel):
53 - connectionSuccessful: bool
54 - message: str
55 -
56 -
57 -class UpdateConnector(BaseModel):
58 - connector_url: str
59 - connector_username: Optional[str]
60 - connector_password: Optional[str]
61 - connector_api_key: Optional[str]
backend/app/connectors/services.py deleted
-284
@@ -1,284 +0,0 @@
1 -import os
2 -from contextlib import contextmanager
3 -from datetime import datetime
4 -from typing import Generator
5 -from typing import List
6 -from typing import Optional
7 -from typing import Type
8 -
9 -from fastapi import UploadFile
10 -from loguru import logger
11 -from pydantic import BaseModel
12 -from sqlmodel import Session
13 -from sqlmodel import select
14 -from werkzeug.utils import secure_filename
15 -
16 -from app.connectors.cortex.utils.universal import verify_cortex_connection
17 -from app.connectors.dfir_iris.utils.universal import verify_dfir_iris_connection
18 -from app.connectors.graylog.utils.universal import verify_graylog_connection
19 -from app.connectors.models import Connectors
20 -from app.connectors.schema import ConnectorResponse
21 -from app.connectors.shuffle.utils.universal import verify_shuffle_connection
22 -from app.connectors.sublime.utils.universal import verify_sublime_connection
23 -from app.connectors.velociraptor.utils.universal import verify_velociraptor_connection
24 -from app.connectors.wazuh_indexer.utils.universal import verify_wazuh_indexer_connection
25 -from app.connectors.wazuh_manager.utils.universal import verify_wazuh_manager_connection
26 -from app.db.db_session import engine # Import the shared engine
27 -
28 -UPLOAD_FOLDER = "file-store"
29 -UPLOAD_FOLDER = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), UPLOAD_FOLDER)
30 -ALLOWED_EXTENSIONS = set(["yaml"]) # replace with your allowed file extensions
31 -
32 -
33 -# Create an interface for connector services
34 -class ConnectorServiceInterface(BaseModel):
35 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
36 - raise NotImplementedError
37 -
38 -
39 -# Wazuh Manager Service
40 -class WazuhManagerService(ConnectorServiceInterface):
41 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
42 - return verify_wazuh_manager_connection(connector.connector_name)
43 -
44 -
45 -# Wazuh Indexer Service
46 -class WazuhIndexerService(ConnectorServiceInterface):
47 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
48 - return verify_wazuh_indexer_connection(connector.connector_name)
49 -
50 -
51 -# Velociraptor Service
52 -class VelociraptorService(ConnectorServiceInterface):
53 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
54 - return verify_velociraptor_connection(connector.connector_name)
55 -
56 -
57 -# Graylog Service
58 -class GraylogService(ConnectorServiceInterface):
59 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
60 - return verify_graylog_connection(connector.connector_name)
61 -
62 -
63 -# DFIR-IRIS Service
64 -class DfirIrisService(ConnectorServiceInterface):
65 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
66 - return verify_dfir_iris_connection(connector.connector_name)
67 -
68 -
69 -# Cortex Service
70 -class CortexService(ConnectorServiceInterface):
71 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
72 - return verify_cortex_connection(connector.connector_name)
73 -
74 -
75 -# Shuffle Service
76 -class ShuffleService(ConnectorServiceInterface):
77 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
78 - return verify_shuffle_connection(connector.connector_name)
79 -
80 -
81 -# Sublime Service
82 -class SublimeService(ConnectorServiceInterface):
83 - def verify_authentication(self, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
84 - return verify_sublime_connection(connector.connector_name)
85 -
86 -
87 -# Factory function to create a service instance based on connector name
88 -def get_connector_service(connector_name: str) -> Type[ConnectorServiceInterface]:
89 - service_map = {
90 - "Wazuh-Manager": WazuhManagerService,
91 - "Wazuh-Indexer": WazuhIndexerService,
92 - "Velociraptor": VelociraptorService,
93 - "Graylog": GraylogService,
94 - "DFIR-IRIS": DfirIrisService,
95 - "Cortex": CortexService,
96 - "Shuffle": ShuffleService,
97 - "Sublime": SublimeService,
98 - }
99 - return service_map.get(connector_name, None)
100 -
101 -
102 -class ConnectorServices:
103 - """
104 - Service class for handling operations related to connectors.
105 - """
106 -
107 - @staticmethod
108 - @contextmanager
109 - def get_session() -> Generator[Session, None, None]:
110 - """
111 - Get a new session for database interaction.
112 -
113 - This method is a context manager, which ensures that the session is closed
114 - once the operations within the context are completed.
115 -
116 - Yields:
117 - Session: The database session object.
118 - """
119 - session = Session(engine)
120 - try:
121 - yield session
122 - finally:
123 - session.close()
124 -
125 - @classmethod
126 - def fetch_all_connectors(cls) -> List[ConnectorResponse]:
127 - """
128 - Fetch all connectors from the database.
129 -
130 - This method retrieves all connector records from the database, converts them
131 - to Pydantic models, and returns them as a list.
132 -
133 - Returns:
134 - List[ConnectorResponse]: A list of connectors in their Pydantic representation.
135 - """
136 - # Get a new session
137 - with cls.get_session() as session:
138 - query = select(Connectors)
139 - connectors = session.exec(query).all()
140 -
141 - # Convert the SQLModel object to a Pydantic model
142 - connector_responses = [ConnectorResponse.from_orm(connector) for connector in connectors]
143 - return connector_responses
144 -
145 - @classmethod
146 - def fetch_connector_by_id(cls, connector_id: int) -> Optional[ConnectorResponse]:
147 - """
148 - Fetch a connector by its ID from the database.
149 -
150 - Given a connector ID, this method retrieves the corresponding connector
151 - record from the database, if it exists.
152 -
153 - Args:
154 - connector_id (int): The ID of the connector to fetch.
155 -
156 - Returns:
157 - Optional[ConnectorResponse]: The connector in its Pydantic representation, or None if not found.
158 - """
159 - # Get a new session
160 - with cls.get_session() as session:
161 - query = select(Connectors).where(Connectors.id == connector_id)
162 - connector = session.exec(query).first()
163 -
164 - if not connector:
165 - logger.info(f"No connector found for ID: {connector_id}")
166 - return None
167 -
168 - try:
169 - # Convert the SQLModel object to a Pydantic model
170 - connector_response = ConnectorResponse.from_orm(connector)
171 - return connector_response
172 - except Exception as e:
173 - logger.exception(f"Failed to create ConnectorResponse object: {e}")
174 - return None
175 -
176 - @classmethod
177 - def verify_connector_by_id(cls, connector_id: int) -> Optional[ConnectorResponse]:
178 - """
179 - Verify a connector by making an API call to it.
180 -
181 - Given a connector ID, this method retrieves the corresponding connector
182 - record from the database, if it exists, and makes an API call to the connector.
183 -
184 - Args:
185 - connector_id (int): The ID of the connector to verify.
186 -
187 - Returns:
188 - Optional[ConnectorResponse]: The connector in its Pydantic representation, or None if not found.
189 - """
190 - # Get a new session
191 - with cls.get_session() as session:
192 - query = select(Connectors).where(Connectors.id == connector_id)
193 - connector = session.exec(query).first()
194 -
195 - if not connector:
196 - logger.info(f"No connector found for ID: {connector_id}")
197 - return None
198 -
199 - try:
200 - # Convert the SQLModel object to a Pydantic model
201 - connector_response = ConnectorResponse.from_orm(connector)
202 -
203 - # Get the appropriate service for this connector
204 - ServiceClass = get_connector_service(connector_response.connector_name)
205 -
206 - if ServiceClass is not None:
207 - service_instance = ServiceClass()
208 - connector_response = service_instance.verify_authentication(connector_response)
209 - else:
210 - logger.error(f"Connector type {connector_response.connector_name} is not supported")
211 - return None
212 -
213 - return connector_response
214 - except Exception as e:
215 - logger.exception(f"Failed to create ConnectorResponse object: {e}")
216 - return None
217 -
218 - @classmethod
219 - def update_connector_by_id(cls, connector_id: int, connector: ConnectorResponse) -> Optional[ConnectorResponse]:
220 - """
221 - Update a connector by its ID in the database.
222 -
223 - Given a connector ID and a Pydantic representation of a connector, this method
224 - updates the corresponding connector record in the database, if it exists.
225 -
226 - Args:
227 - connector_id (int): The ID of the connector to update.
228 - connector (ConnectorResponse): The updated connector in its Pydantic representation.
229 -
230 - Returns:
231 - Optional[ConnectorResponse]: The updated connector in its Pydantic representation, or None if not found.
232 - """
233 - # Get a new session
234 - with cls.get_session() as session:
235 - query = select(Connectors).where(Connectors.id == connector_id)
236 - connector_record = session.exec(query).first()
237 -
238 - if not connector_record:
239 - logger.info(f"No connector found for ID: {connector_id}")
240 - return None
241 -
242 - try:
243 - # Update the connector record
244 - connector_record.connector_url = connector.connector_url
245 - connector_record.connector_username = connector.connector_username
246 - connector_record.connector_password = connector.connector_password
247 - connector_record.connector_api_key = connector.connector_api_key
248 - connector_record.connector_last_updated = datetime.now()
249 -
250 - # Commit the changes to the database
251 - session.add(connector_record)
252 - session.commit()
253 -
254 - # Convert the SQLModel object to a Pydantic model
255 - connector_response = ConnectorResponse.from_orm(connector_record)
256 - return connector_response
257 - except Exception as e:
258 - logger.exception(f"Failed to update connector: {e}")
259 - return None
260 -
261 - @staticmethod
262 - def allowed_file(filename):
263 - return "." in filename and filename.rsplit(".", 1)[1].lower() in ALLOWED_EXTENSIONS
264 -
265 - @classmethod
266 - def save_file(cls, file: UploadFile):
267 - if file and cls.allowed_file(file.filename):
268 - filename = secure_filename(file.filename)
269 - file_path = os.path.join(UPLOAD_FOLDER, filename)
270 -
271 - # Save the file
272 - with open(file_path, "wb") as buffer:
273 - buffer.write(file.file.read())
274 -
275 - # Update connector
276 - connector = cls.fetch_connector_by_id(6)
277 - connector.connector_configured = True
278 - connector.connector_api_key = file_path
279 - cls.update_connector_by_id(6, connector)
280 -
281 - connector_response = ConnectorResponse.from_orm(connector)
282 - return connector_response
283 - else:
284 - return False
backend/app/connectors/shuffle/routes/workflows.py deleted
-45
@@ -1,45 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import HTTPException
3 -from loguru import logger
4 -
5 -from app.connectors.shuffle.schema.workflows import WorkflowExecutionBodyModel
6 -from app.connectors.shuffle.schema.workflows import WorkflowExecutionResponseModel
7 -from app.connectors.shuffle.schema.workflows import WorkflowsResponse
8 -from app.connectors.shuffle.services.workflows import get_workflow_executions
9 -from app.connectors.shuffle.services.workflows import get_workflows
10 -
11 -shuffle_workflows_router = APIRouter()
12 -
13 -
14 -@shuffle_workflows_router.get("", response_model=WorkflowsResponse, description="Get all workflows")
15 -async def get_all_workflows() -> WorkflowsResponse:
16 - logger.info("Fetching all workflows")
17 - return get_workflows()
18 -
19 -
20 -@shuffle_workflows_router.get("/executions", response_model=WorkflowExecutionResponseModel, description="Get all workflow executions")
21 -async def get_all_workflow_executions() -> WorkflowExecutionResponseModel:
22 - logger.info("Fetching all workflow executions")
23 -
24 - # Initialize an empty list for storing workflow details
25 - workflow_details = []
26 -
27 - # Get the workflow response by awaiting the asynchronous function get_workflows()
28 - workflow_response = await get_all_workflows()
29 -
30 - # Access the workflows attribute from the response
31 - workflows = workflow_response.workflows
32 -
33 - # Check if workflows is not None before proceeding
34 - if workflows:
35 - for workflow in workflows:
36 - workflow_details.append(
37 - {
38 - "workflow_id": workflow["id"],
39 - "workflow_name": workflow["name"],
40 - "status": get_workflow_executions(WorkflowExecutionBodyModel(workflow_id=workflow["id"])),
41 - },
42 - )
43 - return WorkflowExecutionResponseModel(success=True, message="Successfully fetched workflow executions", workflows=workflow_details)
44 - else:
45 - raise HTTPException(status_code=404, detail="No workflows found")
backend/app/connectors/shuffle/schema/workflows.py deleted
-39
@@ -1,39 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import List
4 -from typing import Optional
5 -
6 -from pydantic import BaseModel
7 -from pydantic import Field
8 -
9 -
10 -class WorkflowsResponse(BaseModel):
11 - message: str
12 - success: bool
13 - workflows: Optional[List[Dict[str, Any]]] = Field([], description="The alerts returned from the search.")
14 -
15 -
16 -class WorkflowStatusExecutionModel(BaseModel):
17 - executions: Optional[str] = Field(None, description="Status of workflow executions")
18 - message: str = Field(..., description="Status message")
19 - success: bool = Field(..., description="Success status")
20 -
21 -
22 -class WorkflowExecutionBodyModel(BaseModel):
23 - workflow_id: str = Field(..., description="Unique identifier for the workflow")
24 -
25 -
26 -class WorkflowExecutionStatusResponseModel(BaseModel):
27 - last_run: Optional[str] = Field(..., description="Status of workflow executions")
28 -
29 -
30 -class WorkflowExecutionModel(BaseModel):
31 - status: WorkflowExecutionStatusResponseModel = Field(..., description="Status object")
32 - workflow_id: str = Field(..., description="Unique identifier for the workflow")
33 - workflow_name: str = Field(..., description="Name of the workflow")
34 -
35 -
36 -class WorkflowExecutionResponseModel(BaseModel):
37 - message: str = Field(..., description="Response message")
38 - success: bool = Field(..., description="Success status")
39 - workflows: List[WorkflowExecutionModel] = Field(..., description="List of workflow objects")
backend/app/connectors/shuffle/services/workflows.py deleted
-33
@@ -1,33 +0,0 @@
1 -from loguru import logger
2 -
3 -from app.connectors.shuffle.schema.workflows import WorkflowExecutionBodyModel
4 -from app.connectors.shuffle.schema.workflows import WorkflowExecutionStatusResponseModel
5 -from app.connectors.shuffle.schema.workflows import WorkflowsResponse
6 -from app.connectors.shuffle.utils.universal import send_get_request
7 -
8 -
9 -def get_workflows() -> WorkflowsResponse:
10 - """
11 - Returns a list of workflows.
12 - """
13 - logger.info("Getting workflows")
14 - response = send_get_request("/api/v1/workflows")
15 - if response is None:
16 - return WorkflowsResponse(success=False, message="Failed to get workflows", workflows=[])
17 - return WorkflowsResponse(success=True, message="Successfully fetched workflows", workflows=response["data"])
18 -
19 -
20 -def get_workflow_executions(exection_body: WorkflowExecutionBodyModel) -> WorkflowExecutionStatusResponseModel:
21 - """
22 - Returns a list of workflow executions.
23 - """
24 - logger.info("Getting workflow executions")
25 - response = send_get_request(f"/api/v1/workflows/{exection_body.workflow_id}/executions")
26 - executions = response["data"]
27 - if executions:
28 - status = executions[0]["status"]
29 - if status is None:
30 - status = "Never Ran"
31 - else:
32 - status = "No executions found"
33 - return WorkflowExecutionStatusResponseModel(last_run=status)
backend/app/connectors/shuffle/utils/universal.py deleted
-213
@@ -1,213 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import Optional
4 -
5 -import requests
6 -from loguru import logger
7 -
8 -from app.connectors.utils import get_connector_info_from_db
9 -
10 -
11 -def verify_shuffle_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
12 - """
13 - Verifies the connection to Shuffle service.
14 -
15 - Returns:
16 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
17 - """
18 - logger.info(
19 - f"Verifying the Shuffle connection to {attributes['connector_url']}",
20 - )
21 - try:
22 - headers = {
23 - "Authorization": f"Bearer {attributes['connector_api_key']}",
24 - }
25 - shuffle_apps = requests.get(
26 - f"{attributes['connector_url']}/api/v1/apps/authentication",
27 - headers=headers,
28 - verify=False,
29 - )
30 - if shuffle_apps.status_code == 200:
31 - logger.info(
32 - f"Connection to {attributes['connector_url']} successful",
33 - )
34 - return {"connectionSuccessful": True, "message": "Shuffle connection successful"}
35 - else:
36 - logger.error(
37 - f"Connection to {attributes['connector_url']} failed with error: {shuffle_apps.text}",
38 - )
39 - return {
40 - "connectionSuccessful": False,
41 - "message": f"Connection to {attributes['connector_url']} failed with error: {shuffle_apps.text}",
42 - }
43 - except Exception as e:
44 - logger.error(
45 - f"Connection to {attributes['connector_url']} failed with error: {e}",
46 - )
47 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error: {e}"}
48 -
49 -
50 -def verify_shuffle_connection(connector_name: str) -> str:
51 - """
52 - Returns if connection to Shuffle service is successful.
53 - """
54 - logger.info("Getting Shuffle authentication token")
55 - attributes = get_connector_info_from_db(connector_name)
56 - if attributes is None:
57 - logger.error("No Shuffle connector found in the database")
58 - return None
59 - return verify_shuffle_credentials(attributes)
60 -
61 -
62 -def send_get_request(endpoint: str, params: Optional[Dict[str, Any]] = None, connector_name: str = "Shuffle") -> Dict[str, Any]:
63 - """
64 - Sends a GET request to the Shuffle service.
65 -
66 - Args:
67 - endpoint (str): The endpoint to send the GET request to.
68 - params (Optional[Dict[str, Any]], optional): The parameters to send with the GET request. Defaults to None.
69 - connector_name (str, optional): The name of the connector to use. Defaults to "Shuffle".
70 -
71 - Returns:
72 - Dict[str, Any]: The response from the GET request.
73 - """
74 - logger.info(f"Sending GET request to {endpoint}")
75 - attributes = get_connector_info_from_db(connector_name)
76 - if attributes is None:
77 - logger.error("No Graylog connector found in the database")
78 - return None
79 - try:
80 - HEADERS = {
81 - "Authorization": f"Bearer {attributes['connector_api_key']}",
82 - }
83 - response = requests.get(
84 - f"{attributes['connector_url']}{endpoint}",
85 - headers=HEADERS,
86 - params=params,
87 - verify=False,
88 - )
89 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
90 - except Exception as e:
91 - logger.error(f"Failed to send GET request to {endpoint} with error: {e}")
92 - return {"success": False, "message": f"Failed to send GET request to {endpoint} with error: {e}"}
93 -
94 -
95 -def send_post_request(endpoint: str, data: Dict[str, Any] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
96 - """
97 - Sends a POST request to the Graylog service.
98 -
99 - Args:
100 - endpoint (str): The endpoint to send the POST request to.
101 - data (Dict[str, Any]): The data to send with the POST request.
102 - connector_name (str, optional): The name of the connector to use. Defaults to "Graylog".
103 -
104 - Returns:
105 - Dict[str, Any]: The response from the POST request.
106 - """
107 - logger.info(f"Sending POST request to {endpoint}")
108 - attributes = get_connector_info_from_db(connector_name)
109 - if attributes is None:
110 - logger.error("No Graylog connector found in the database")
111 - return {"success": False, "message": "No Graylog connector found in the database"}
112 -
113 - try:
114 - HEADERS = {
115 - "Authorization": f"Bearer {attributes['connector_api_key']}",
116 - }
117 - response = requests.post(
118 - f"{attributes['connector_url']}{endpoint}",
119 - headers=HEADERS,
120 - auth=(
121 - attributes["connector_username"],
122 - attributes["connector_password"],
123 - ),
124 - json=data,
125 - verify=False,
126 - )
127 -
128 - if response.status_code == 204:
129 - return {"data": None, "success": True, "message": "Successfully completed request with no content"}
130 - else:
131 - return {
132 - "data": response.json(),
133 - "success": False if response.status_code >= 400 else True,
134 - "message": "Successfully retrieved data" if response.status_code < 400 else "Failed to retrieve data",
135 - }
136 - except Exception as e:
137 - logger.debug(f"Response: {response}")
138 - logger.error(f"Failed to send POST request to {endpoint} with error: {e}")
139 - return {"success": False, "message": f"Failed to send POST request to {endpoint} with error: {e}"}
140 -
141 -
142 -def send_delete_request(endpoint: str, params: Optional[Dict[str, Any]] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
143 - """
144 - Sends a DELETE request to the Graylog service.
145 -
146 - Args:
147 - endpoint (str): The endpoint to send the DELETE request to.
148 - params (Optional[Dict[str, Any]], optional): The parameters to send with the DELETE request. Defaults to None.
149 - connector_name (str, optional): The name of the connector to use. Defaults to "Graylog".
150 -
151 - Returns:
152 - Dict[str, Any]: The response from the DELETE request.
153 - """
154 - logger.info(f"Sending DELETE request to {endpoint}")
155 - attributes = get_connector_info_from_db(connector_name)
156 - if attributes is None:
157 - logger.error("No Graylog connector found in the database")
158 - return None
159 - try:
160 - HEADERS = {
161 - "Authorization": f"Bearer {attributes['connector_api_key']}",
162 - }
163 - response = requests.delete(
164 - f"{attributes['connector_url']}{endpoint}",
165 - headers=HEADERS,
166 - auth=(
167 - attributes["connector_username"],
168 - attributes["connector_password"],
169 - ),
170 - params=params,
171 - verify=False,
172 - )
173 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
174 - except Exception as e:
175 - logger.error(f"Failed to send DELETE request to {endpoint} with error: {e}")
176 - return {"success": False, "message": f"Failed to send DELETE request to {endpoint} with error: {e}"}
177 -
178 -
179 -def send_put_request(endpoint: str, data: Optional[Dict[str, Any]] = None, connector_name: str = "Graylog") -> Dict[str, Any]:
180 - """
181 - Sends a PUT request to the Graylog service.
182 -
183 - Args:
184 - endpoint (str): The endpoint to send the PUT request to.
185 - data (Optional[Dict[str, Any]]): The data to send with the PUT request.
186 - connector_name (str, optional): The name of the connector to use. Defaults to "Graylog".
187 -
188 - Returns:
189 - Dict[str, Any]: The response from the PUT request.
190 - """
191 - logger.info(f"Sending PUT request to {endpoint}")
192 - attributes = get_connector_info_from_db(connector_name)
193 - if attributes is None:
194 - logger.error("No Graylog connector found in the database")
195 - return None
196 - try:
197 - HEADERS = {
198 - "Authorization": f"Bearer {attributes['connector_api_key']}",
199 - }
200 - response = requests.put(
201 - f"{attributes['connector_url']}{endpoint}",
202 - headers=HEADERS,
203 - auth=(
204 - attributes["connector_username"],
205 - attributes["connector_password"],
206 - ),
207 - json=data,
208 - verify=False,
209 - )
210 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
211 - except Exception as e:
212 - logger.error(f"Failed to send PUT request to {endpoint} with error: {e}")
213 - return {"success": False, "message": f"Failed to send PUT request to {endpoint} with error: {e}"}
backend/app/connectors/sublime/models/alerts.py deleted
-79
@@ -1,79 +0,0 @@
1 -import datetime
2 -from typing import List
3 -from typing import Optional
4 -
5 -from sqlmodel import Field
6 -from sqlmodel import Relationship
7 -from sqlmodel import SQLModel
8 -
9 -
10 -class FlaggedRule(SQLModel, table=True):
11 - id: Optional[int] = Field(default=None, primary_key=True)
12 - rule_id: str
13 - name: str
14 - severity: Optional[str] = Field(None, description="Severity level of the flagged rule")
15 - tags: str
16 - sublime_alert_id: int = Field(foreign_key="sublimealerts.id")
17 -
18 - # Relationship attribute
19 - sublime_alert: "SublimeAlerts" = Relationship(back_populates="flagged_rules")
20 -
21 -
22 -class Mailbox(SQLModel, table=True):
23 - id: Optional[int] = Field(default=None, primary_key=True)
24 - external_id: Optional[str] = Field(None, description="External identifier for the mailbox")
25 - mailbox_id: str
26 - sublime_alert_id: int = Field(foreign_key="sublimealerts.id")
27 -
28 - # Relationship attribute
29 - sublime_alert: "SublimeAlerts" = Relationship(back_populates="mailbox")
30 -
31 -
32 -class TriggeredAction(SQLModel, table=True):
33 - id: Optional[int] = Field(default=None, primary_key=True)
34 - action_id: str
35 - name: str
36 - type: str
37 - sublime_alert_id: int = Field(foreign_key="sublimealerts.id")
38 -
39 - # Relationship attribute
40 - sublime_alert: "SublimeAlerts" = Relationship(back_populates="triggered_actions")
41 -
42 -
43 -class Sender(SQLModel, table=True):
44 - id: Optional[int] = Field(default=None, primary_key=True)
45 - email: str
46 - name: Optional[str] = Field(None, description="Name of the sender")
47 - sublime_alert_id: int = Field(foreign_key="sublimealerts.id")
48 -
49 - # Relationship attribute
50 - sublime_alert: "SublimeAlerts" = Relationship(back_populates="sender")
51 -
52 -
53 -class Recipient(SQLModel, table=True):
54 - id: Optional[int] = Field(default=None, primary_key=True)
55 - email: str
56 - name: Optional[str] = Field(None, description="Name of the recipient")
57 - sublime_alert_id: int = Field(foreign_key="sublimealerts.id")
58 -
59 - # Relationship attribute
60 - sublime_alert: "SublimeAlerts" = Relationship(back_populates="recipients")
61 -
62 -
63 -class SublimeAlerts(SQLModel, table=True):
64 - id: Optional[int] = Field(default=None, primary_key=True)
65 - api_version: str
66 - created_at: str
67 - event_id: str
68 - type: str
69 - message_id: str
70 - canonical_id: str
71 - external_id: str
72 - message_source_id: str
73 - timestamp: datetime.datetime = datetime.datetime.now()
74 -
75 - flagged_rules: List[FlaggedRule] = Relationship(back_populates="sublime_alert")
76 - mailbox: List[Mailbox] = Relationship(back_populates="sublime_alert")
77 - triggered_actions: List[TriggeredAction] = Relationship(back_populates="sublime_alert")
78 - sender: List[Sender] = Relationship(back_populates="sublime_alert")
79 - recipients: List[Recipient] = Relationship(back_populates="sublime_alert")
backend/app/connectors/sublime/routes/alerts.py deleted
-35
@@ -1,35 +0,0 @@
1 -from fastapi import APIRouter
2 -from loguru import logger
3 -
4 -from app.connectors.sublime.schema.alerts import AlertRequestBody
5 -from app.connectors.sublime.schema.alerts import AlertResponseBody
6 -from app.connectors.sublime.schema.alerts import SublimeAlertsResponse
7 -from app.connectors.sublime.services.alerts import collect_alerts
8 -from app.connectors.sublime.services.alerts import store_sublime_alert
9 -
10 -sublime_alerts_router = APIRouter()
11 -
12 -
13 -@sublime_alerts_router.post("/alert", description="Receive alert from Sublime and store it in the database")
14 -async def receive_sublime_alert(alert_request_body: AlertRequestBody) -> AlertResponseBody:
15 - """
16 - Endpoint to store alert in the `sublimealerts` table.
17 - Invoked by the Sublime alert webhook which is configured in the Sublime UI.
18 -
19 - Returns:
20 - jsonify: A JSON response containing if the alert was stored successfully.
21 - """
22 - logger.info(f"Received alert from Sublime: {alert_request_body}")
23 - return store_sublime_alert(alert_request_body)
24 -
25 -
26 -@sublime_alerts_router.get("/alerts", response_model=SublimeAlertsResponse, description="Get all alerts")
27 -async def get_sublime_alerts() -> SublimeAlertsResponse:
28 - """
29 - Endpoint to retrieve alerts from the `sublimealerts` table.
30 -
31 - Returns:
32 - jsonify: A JSON response containing all the alerts stored in the `sublimealerts` table.
33 - """
34 - logger.info("Fetching all alerts from Sublime")
35 - return collect_alerts()
backend/app/connectors/sublime/schema/alerts.py deleted
-121
@@ -1,121 +0,0 @@
1 -import datetime
2 -from typing import List
3 -from typing import Optional
4 -
5 -from pydantic import BaseModel
6 -from pydantic import Field
7 -
8 -
9 -class FlaggedRule(BaseModel):
10 - id: str = Field(..., description="Unique identifier for the flagged rule")
11 - name: str = Field(..., description="Name of the flagged rule")
12 - severity: Optional[str] = Field(None, description="Severity level of the flagged rule")
13 - tags: List[str] = Field(..., description="List of tags associated with the flagged rule")
14 -
15 -
16 -class Mailbox(BaseModel):
17 - external_id: Optional[str] = Field(None, description="External identifier for the mailbox")
18 - id: str = Field(..., description="Unique identifier for the mailbox")
19 -
20 -
21 -class Message(BaseModel):
22 - canonical_id: str = Field(..., description="Canonical identifier for the message")
23 - external_id: Optional[str] = Field(None, description="External identifier for the mailbox")
24 - id: str = Field(..., description="Unique identifier for the message")
25 - mailbox: Mailbox = Field(..., description="Mailbox details")
26 - message_source_id: str = Field(..., description="Source identifier for the message")
27 -
28 -
29 -class TriggeredAction(BaseModel):
30 - id: str = Field(..., description="Unique identifier for the triggered action")
31 - name: str = Field(..., description="Name of the triggered action")
32 - type: str = Field(..., description="Type of the triggered action")
33 -
34 -
35 -class Data(BaseModel):
36 - flagged_rules: List[FlaggedRule] = Field(..., description="List of flagged rules")
37 - message: Message = Field(..., description="Message details")
38 - triggered_actions: List[TriggeredAction] = Field(..., description="List of triggered actions")
39 -
40 -
41 -class AlertRequestBody(BaseModel):
42 - api_version: str = Field(..., description="API version", alias="api_version")
43 - created_at: str = Field(..., description="Creation timestamp in ISO 8601 format", alias="created_at")
44 - data: Data = Field(..., description="Nested data object")
45 - id: str = Field(..., description="Unique identifier for the request body")
46 - type: str = Field(..., description="Type of event, e.g., message.flagged")
47 -
48 -
49 -class AlertResponseBody(BaseModel):
50 - success: bool = Field(..., description="Success status of the request")
51 - message: str = Field(..., description="Message describing the result of the request")
52 -
53 -
54 -### SQLModel Schema
55 -class FlaggedRuleSchema(BaseModel):
56 - rule_id: str
57 - name: str
58 - severity: Optional[str] = Field(None, description="Severity level of the flagged rule")
59 - tags: str
60 -
61 - class Config:
62 - orm_mode = True
63 -
64 -
65 -class MailboxSchema(BaseModel):
66 - external_id: Optional[str] = Field(None, description="External identifier for the mailbox")
67 - mailbox_id: str
68 -
69 - class Config:
70 - orm_mode = True
71 -
72 -
73 -class TriggeredActionSchema(BaseModel):
74 - action_id: str
75 - name: str
76 - type: str
77 -
78 - class Config:
79 - orm_mode = True
80 -
81 -
82 -class SenderSchema(BaseModel):
83 - email: str
84 - name: str
85 -
86 - class Config:
87 - orm_mode = True
88 -
89 -
90 -class RecipientSchema(BaseModel):
91 - email: str
92 - name: str
93 -
94 - class Config:
95 - orm_mode = True
96 -
97 -
98 -class SublimeAlertsSchema(BaseModel):
99 - api_version: str
100 - created_at: str
101 - event_id: str
102 - type: str
103 - message_id: str
104 - canonical_id: str
105 - external_id: str
106 - message_source_id: str
107 - timestamp: datetime.datetime
108 - flagged_rules: List[FlaggedRuleSchema]
109 - mailbox: List[MailboxSchema]
110 - triggered_actions: List[TriggeredActionSchema]
111 - sender: List[SenderSchema]
112 - recipients: List[RecipientSchema]
113 -
114 - class Config:
115 - orm_mode = True
116 -
117 -
118 -class SublimeAlertsResponse(BaseModel):
119 - sublime_alerts: List[SublimeAlertsSchema]
120 - success: bool
121 - message: str
backend/app/connectors/sublime/services/alerts.py deleted
-147
@@ -1,147 +0,0 @@
1 -import json
2 -from typing import List
3 -
4 -from fastapi import HTTPException
5 -from loguru import logger
6 -
7 -from app.connectors.sublime.models.alerts import FlaggedRule
8 -from app.connectors.sublime.models.alerts import Mailbox
9 -from app.connectors.sublime.models.alerts import Recipient
10 -from app.connectors.sublime.models.alerts import Sender
11 -from app.connectors.sublime.models.alerts import SublimeAlerts
12 -from app.connectors.sublime.models.alerts import TriggeredAction
13 -from app.connectors.sublime.schema.alerts import AlertRequestBody
14 -from app.connectors.sublime.schema.alerts import AlertResponseBody
15 -from app.connectors.sublime.schema.alerts import SublimeAlertsResponse
16 -from app.connectors.sublime.schema.alerts import SublimeAlertsSchema
17 -from app.connectors.sublime.utils.universal import send_get_request
18 -from app.db.db_session import session
19 -
20 -
21 -def create_sublime_alert(alert_request_body: AlertRequestBody) -> SublimeAlerts:
22 - return SublimeAlerts(
23 - api_version=alert_request_body.api_version,
24 - created_at=alert_request_body.created_at,
25 - event_id=alert_request_body.id,
26 - type=alert_request_body.type,
27 - message_id=alert_request_body.data.message.id,
28 - canonical_id=alert_request_body.data.message.canonical_id,
29 - external_id=alert_request_body.data.message.external_id,
30 - message_source_id=alert_request_body.data.message.message_source_id,
31 - )
32 -
33 -
34 -def create_flagged_rules(alert_request_body: AlertRequestBody, sublime_alert_id: int) -> List[FlaggedRule]:
35 - flagged_rules = []
36 - for rule in alert_request_body.data.flagged_rules:
37 - tags_str = json.dumps(rule.tags)
38 - flagged_rules.append(
39 - FlaggedRule(rule_id=rule.id, name=rule.name, severity=rule.severity, tags=tags_str, sublime_alert_id=sublime_alert_id),
40 - )
41 - return flagged_rules
42 -
43 -
44 -def create_mailbox(alert_request_body: AlertRequestBody, sublime_alert_id: int) -> Mailbox:
45 - return Mailbox(
46 - external_id=alert_request_body.data.message.mailbox.external_id,
47 - mailbox_id=alert_request_body.data.message.mailbox.id,
48 - sublime_alert_id=sublime_alert_id,
49 - )
50 -
51 -
52 -def create_triggered_actions(alert_request_body: AlertRequestBody, sublime_alert_id: int) -> List[TriggeredAction]:
53 - triggered_actions = []
54 - for action in alert_request_body.data.triggered_actions:
55 - triggered_actions.append(
56 - TriggeredAction(action_id=action.id, name=action.name, type=action.type, sublime_alert_id=sublime_alert_id),
57 - )
58 - return triggered_actions
59 -
60 -
61 -def store_sublime_alert(alert_request_body: AlertRequestBody) -> AlertResponseBody:
62 - try:
63 - sublime_alert = create_sublime_alert(alert_request_body)
64 - session.add(sublime_alert)
65 - session.flush()
66 -
67 - flagged_rules = create_flagged_rules(alert_request_body, sublime_alert.id)
68 - mailbox = create_mailbox(alert_request_body, sublime_alert.id)
69 - triggered_actions = create_triggered_actions(alert_request_body, sublime_alert.id)
70 - sender = create_sender(alert_request_body, sublime_alert.id)
71 - recipient = create_recipient(alert_request_body, sublime_alert.id)
72 -
73 - session.add_all(flagged_rules)
74 - session.add(mailbox)
75 - session.add_all(triggered_actions)
76 - session.add(sender)
77 - session.add(recipient)
78 -
79 - logger.info(f"Preparing to store: {sublime_alert}")
80 - session.commit()
81 - logger.info(f"Alert {alert_request_body.id} stored in the database")
82 -
83 - return AlertResponseBody(success=True, message=f"Alert {alert_request_body.id} stored in the database")
84 - except Exception as e:
85 - logger.error(f"Failed to store alert {alert_request_body.id} in the database: {e}")
86 - raise HTTPException(status_code=500, detail=f"Failed to store alert {alert_request_body.id} in the database: {e}")
87 -
88 -
89 -def create_sender(alert_request_body: AlertRequestBody, sublime_alert_id: int) -> Sender:
90 - return Sender(email=collect_sender(alert_request_body.data.message.id), name="n/a", sublime_alert_id=sublime_alert_id)
91 -
92 -
93 -def create_recipient(alert_request_body: AlertRequestBody, sublime_alert_id: int) -> Recipient:
94 - return Recipient(email=collect_recipient(alert_request_body.data.message.id), name="n/a", sublime_alert_id=sublime_alert_id)
95 -
96 -
97 -def collect_sender(message_id: str) -> Sender:
98 - """
99 - Get a single Sublime Alert from the database
100 - """
101 - logger.info(f"Getting Sublime Alert with message_id {message_id}")
102 - message_details = send_get_request(f"/v0/messages/{message_id}")
103 - if not message_details["success"]:
104 - logger.error(f"Failed to get Sublime Alert with message_id {message_id}: {message_details['message']}")
105 - raise HTTPException(
106 - status_code=500,
107 - detail=f"Failed to get Sublime Alert with message_id {message_id}: {message_details['message']}",
108 - )
109 - logger.info(f"Successfully retrieved Sublime Alert with message_id {message_id}")
110 - return message_details["data"]["sender"]["email"]
111 -
112 -
113 -def collect_recipient(message_id: str) -> Recipient:
114 - """
115 - Get a single Sublime Alert from the database
116 - """
117 - logger.info(f"Getting Sublime Alert with message_id {message_id}")
118 - message_details = send_get_request(f"/v0/messages/{message_id}")
119 - if not message_details["success"]:
120 - logger.error(f"Failed to get Sublime Alert with message_id {message_id}: {message_details['message']}")
121 - raise HTTPException(
122 - status_code=500,
123 - detail=f"Failed to get Sublime Alert with message_id {message_id}: {message_details['message']}",
124 - )
125 - logger.info(f"Successfully retrieved Sublime Alert with message_id {message_id}")
126 - return message_details["data"]["recipients"][0]["email"]
127 -
128 -
129 -def collect_alerts() -> List[SublimeAlertsResponse]:
130 - """
131 - Get all Sublime Alerts from the database
132 - """
133 - logger.info("Getting all Sublime Alerts")
134 - alerts = session.query(SublimeAlerts).all()
135 - # Also add the relationships
136 - for alert in alerts:
137 - alert.flagged_rules = session.query(FlaggedRule).filter(FlaggedRule.sublime_alert_id == alert.id).all()
138 - alert.mailbox = [session.query(Mailbox).filter(Mailbox.sublime_alert_id == alert.id).first()]
139 - alert.triggered_actions = session.query(TriggeredAction).filter(TriggeredAction.sublime_alert_id == alert.id).all()
140 - alert.sender = [session.query(Sender).filter(Sender.sublime_alert_id == alert.id).first()]
141 - alert.recipients = session.query(Recipient).filter(Recipient.sublime_alert_id == alert.id).all()
142 - logger.info("Successfully retrieved all Sublime Alerts")
143 - return SublimeAlertsResponse(
144 - success=True,
145 - message="Successfully retrieved all Sublime Alerts",
146 - sublime_alerts=[SublimeAlertsSchema.from_orm(alert) for alert in alerts],
147 - )
backend/app/connectors/sublime/utils/universal.py deleted
-98
@@ -1,98 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import Optional
4 -
5 -import requests
6 -from loguru import logger
7 -
8 -from app.connectors.utils import get_connector_info_from_db
9 -
10 -
11 -def verify_sublime_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
12 - """
13 - Verifies the connection to Sublime service.
14 -
15 - Returns:
16 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
17 - """
18 - logger.info(
19 - f"Verifying the Sublime connection to {attributes['connector_url']}",
20 - )
21 - try:
22 - headers = {
23 - "Authorization": f"Bearer {attributes['connector_api_key']}",
24 - "Content-Type": "application/json",
25 - }
26 - params = {
27 - "limit": 1,
28 - }
29 - sublime = requests.get(
30 - f"{attributes['connector_url']}/v0/rules",
31 - headers=headers,
32 - params=params,
33 - verify=False,
34 - )
35 - if sublime.status_code == 200:
36 - logger.info(
37 - f"Connection to {attributes['connector_url']} successful",
38 - )
39 - return {"connectionSuccessful": True, "message": "Sublime connection successful"}
40 - else:
41 - logger.error(
42 - f"Connection to {attributes['connector_url']} failed with error: {sublime.text}",
43 - )
44 - return {
45 - "connectionSuccessful": False,
46 - "message": f"Connection to {attributes['connector_url']} failed with error: {sublime.text}",
47 - }
48 - except Exception as e:
49 - logger.error(
50 - f"Connection to {attributes['connector_url']} failed with error: {e}",
51 - )
52 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error: {e}"}
53 -
54 -
55 -def verify_sublime_connection(connector_name: str) -> str:
56 - """
57 - Returns if connection to Sublime service is successful.
58 - """
59 - logger.info("Getting Sublime authentication token")
60 - attributes = get_connector_info_from_db(connector_name)
61 - if attributes is None:
62 - logger.error("No Sublime connector found in the database")
63 - return None
64 - return verify_sublime_credentials(attributes)
65 -
66 -
67 -def send_get_request(endpoint: str, params: Optional[Dict[str, Any]] = None, connector_name: str = "Sublime") -> Dict[str, Any]:
68 - """
69 - Sends a GET request to the Sublime service.
70 -
71 - Args:
72 - endpoint (str): The endpoint to send the GET request to.
73 - params (Optional[Dict[str, Any]], optional): The parameters to send with the GET request. Defaults to None.
74 - connector_name (str, optional): The name of the connector to use. Defaults to "Shuffle".
75 -
76 - Returns:
77 - Dict[str, Any]: The response from the GET request.
78 - """
79 - logger.info(f"Sending GET request to {endpoint}")
80 - attributes = get_connector_info_from_db(connector_name)
81 - if attributes is None:
82 - logger.error("No Sublime connector found in the database")
83 - return None
84 - try:
85 - HEADERS = {
86 - "Authorization": f"Bearer {attributes['connector_api_key']}",
87 - "Content-Type": "application/json",
88 - }
89 - response = requests.get(
90 - f"{attributes['connector_url']}{endpoint}",
91 - headers=HEADERS,
92 - params=params,
93 - verify=False,
94 - )
95 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
96 - except Exception as e:
97 - logger.error(f"Failed to send GET request to {endpoint} with error: {e}")
98 - return {"success": False, "message": f"Failed to send GET request to {endpoint} with error: {e}"}
backend/app/connectors/utils.py deleted
-23
@@ -1,23 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -
4 -from loguru import logger
5 -from sqlmodel import Session
6 -from sqlmodel import select
7 -
8 -from app.connectors.models import Connectors
9 -from app.connectors.schema import ConnectorResponse
10 -from app.db.db_session import engine # Import the shared engine
11 -
12 -
13 -def get_connector_info_from_db(connector_name: str) -> Dict[str, Any]:
14 - with Session(engine) as session:
15 - query = select(Connectors).where(Connectors.connector_name == connector_name)
16 - connector = session.exec(query).first()
17 - if connector:
18 - connector_pydantic = ConnectorResponse.from_orm(connector)
19 - connector_dict = connector_pydantic.dict()
20 - return connector_dict
21 - else:
22 - logger.warning("No connector found.")
23 - return None
backend/app/connectors/velociraptor/routes/artifacts.py deleted
-157
@@ -1,157 +0,0 @@
1 -from typing import List
2 -
3 -from fastapi import APIRouter
4 -from fastapi import Depends
5 -from fastapi import HTTPException
6 -from loguru import logger
7 -
8 -from app.connectors.velociraptor.schema.artifacts import ArtifactsResponse
9 -from app.connectors.velociraptor.schema.artifacts import CollectArtifactBody
10 -from app.connectors.velociraptor.schema.artifacts import CollectArtifactResponse
11 -from app.connectors.velociraptor.schema.artifacts import OSPrefixEnum
12 -from app.connectors.velociraptor.schema.artifacts import OSPrefixModel
13 -from app.connectors.velociraptor.schema.artifacts import QuarantineBody
14 -from app.connectors.velociraptor.schema.artifacts import QuarantineResponse
15 -from app.connectors.velociraptor.schema.artifacts import RunCommandBody
16 -from app.connectors.velociraptor.schema.artifacts import RunCommandResponse
17 -from app.connectors.velociraptor.services.artifacts import get_artifacts
18 -from app.connectors.velociraptor.services.artifacts import quarantine_host
19 -from app.connectors.velociraptor.services.artifacts import run_artifact_collection
20 -from app.connectors.velociraptor.services.artifacts import run_remote_command
21 -from app.db.db_session import session
22 -from app.db.universal_models import Agents
23 -
24 -# App specific imports
25 -
26 -
27 -velociraptor_artifacts_router = APIRouter()
28 -
29 -
30 -# Get all valid OS prefixes
31 -def get_valid_os_prefixes() -> List[str]:
32 - return [prefix.name.lower() for prefix in OSPrefixEnum]
33 -
34 -
35 -# Verify the OS prefix exists and return the appropriate Enum value
36 -def verify_os_prefix_exists(os_prefix: str) -> str:
37 - os_prefix_lower = os_prefix.lower()
38 - os_prefix_upper = os_prefix.upper() # Convert to uppercase for Enum matching
39 - valid_os_prefixes = get_valid_os_prefixes()
40 -
41 - if os_prefix_lower not in valid_os_prefixes:
42 - raise HTTPException(status_code=400, detail=f"OS prefix {os_prefix} does not exist.")
43 -
44 - return OSPrefixEnum[os_prefix_upper].value # Use the uppercase version for Enum matching
45 -
46 -
47 -def get_os_prefix_from_os_name(os_name: str) -> str:
48 - # Use the OSPrefixModel to get the OS prefix from the OS name
49 - logger.info(f"Getting OS prefix from OS name {os_name}")
50 - os_prefix_model = OSPrefixModel(os_name=os_name)
51 - result = os_prefix_model.get_os_prefix()
52 - logger.info(f"OS prefix for OS name {os_name} is {result}")
53 - return result
54 -
55 -
56 -def get_velociraptor_id(hostname: str) -> str:
57 - # Get the velociraptor_id from the hostname
58 - logger.info(f"Getting velociraptor_id from hostname {hostname}")
59 - agent = session.query(Agents).filter(Agents.hostname == hostname).first()
60 - if not agent:
61 - raise HTTPException(status_code=404, detail=f"Agent with hostname {hostname} not found")
62 - velociraptor_id = agent.velociraptor_id
63 - # If the velociraptor_id is `n/a`, raise an error
64 - if velociraptor_id == "n/a":
65 - raise HTTPException(status_code=404, detail=f"Velociraptor ID for hostname {hostname} is not available")
66 - logger.info(f"velociraptor_id for hostname {hostname} is {velociraptor_id}")
67 - return velociraptor_id
68 -
69 -
70 -@velociraptor_artifacts_router.get("", response_model=ArtifactsResponse, description="Get all artifacts")
71 -async def get_all_artifacts() -> ArtifactsResponse:
72 - logger.info("Fetching all artifacts")
73 - return get_artifacts()
74 -
75 -
76 -@velociraptor_artifacts_router.get(
77 - "/{os_prefix}",
78 - response_model=ArtifactsResponse,
79 - description="Get all artifacts for a specific OS prefix",
80 -)
81 -async def get_all_artifacts_for_os_prefix(os_prefix: str = Depends(verify_os_prefix_exists)) -> ArtifactsResponse:
82 - logger.info(f"Fetching all artifacts for OS prefix {os_prefix}")
83 - # Get all the artifacts names that begin with the OS prefix
84 - artifacts = get_artifacts().artifacts
85 - artifacts_for_os_prefix = [artifact for artifact in artifacts if artifact.name.startswith(os_prefix)]
86 - return ArtifactsResponse(success=True, message=f"All artifacts for OS prefix {os_prefix} retrieved", artifacts=artifacts_for_os_prefix)
87 -
88 -
89 -@velociraptor_artifacts_router.get(
90 - "/hostname/{hostname}",
91 - response_model=ArtifactsResponse,
92 - description="Get all artifacts for a specific host's OS prefix",
93 -)
94 -async def get_all_artifacts_for_hostname(hostname: str) -> ArtifactsResponse:
95 - logger.info(f"Fetching all artifacts for hostname {hostname}")
96 - agent = session.query(Agents).filter(Agents.hostname == hostname).first()
97 - if not agent:
98 - raise HTTPException(status_code=404, detail=f"Agent with hostname {hostname} not found")
99 - os_prefix = get_os_prefix_from_os_name(os_name=agent.os.lower())
100 - if not os_prefix:
101 - raise HTTPException(status_code=404, detail=f"OS prefix of {agent.os.lower()} for hostname {hostname} not found")
102 - result = await get_all_artifacts_for_os_prefix(os_prefix)
103 - return ArtifactsResponse(
104 - success=True,
105 - message=f"All available artifacts that can be ran for hostname {hostname} retrieved",
106 - artifacts=result.artifacts,
107 - )
108 -
109 -
110 -@velociraptor_artifacts_router.post("/collect", response_model=CollectArtifactResponse, description="Run an analyzer")
111 -async def collect_artifact(collect_artifact_body: CollectArtifactBody) -> CollectArtifactResponse:
112 - logger.info(f"Received request to collect artifact {collect_artifact_body}")
113 - # Check that provided artifact name applies for the provided hostname and use the `get_all_artifacts_for_hostname` function to get the list of artifacts
114 - result = await get_all_artifacts_for_hostname(collect_artifact_body.hostname)
115 - artifact_names = [artifact.name for artifact in result.artifacts]
116 - if collect_artifact_body.artifact_name not in artifact_names:
117 - raise HTTPException(
118 - status_code=400,
119 - detail=f"Artifact name {collect_artifact_body.artifact_name} does not apply for hostname {collect_artifact_body.hostname} or does not exist",
120 - )
121 - # Add the velociraptor_id to the run_analyzer_body object
122 - collect_artifact_body.velociraptor_id = get_velociraptor_id(collect_artifact_body.hostname)
123 - # Run the analyzer
124 - return run_artifact_collection(collect_artifact_body)
125 -
126 -
127 -@velociraptor_artifacts_router.post("/command", response_model=RunCommandResponse, description="Run a remote command")
128 -async def run_command(run_command_body: RunCommandBody) -> RunCommandResponse:
129 - logger.info(f"Received request to run command {run_command_body}")
130 - result = await get_all_artifacts_for_hostname(run_command_body.hostname)
131 - artifact_names = [artifact.name for artifact in result.artifacts]
132 - if run_command_body.artifact_name not in artifact_names:
133 - raise HTTPException(
134 - status_code=400,
135 - detail=f"Artifact name {run_command_body.artifact_name.value} does not apply for hostname {run_command_body.hostname} or does not exist",
136 - )
137 - # Add the velociraptor_id to the run_command_body object
138 - run_command_body.velociraptor_id = get_velociraptor_id(run_command_body.hostname)
139 - # Run the command
140 - return run_remote_command(run_command_body)
141 -
142 -
143 -@velociraptor_artifacts_router.post("/quarantine", response_model=QuarantineResponse, description="Quarantine a host")
144 -async def quarantine(quarantine_body: QuarantineBody) -> QuarantineResponse:
145 - logger.info(f"Received request to quarantine host {quarantine_body}")
146 - result = await get_all_artifacts_for_hostname(quarantine_body.hostname)
147 - artifact_names = [artifact.name for artifact in result.artifacts]
148 - if quarantine_body.artifact_name not in artifact_names:
149 - raise HTTPException(
150 - status_code=400,
151 - detail=f"Artifact name {quarantine_body.artifact_name.value} does not apply for hostname {quarantine_body.hostname} or does not exist",
152 - )
153 - # Add the velociraptor_id to the run_command_body object
154 - # Add the velociraptor_id to the quarantine_body object
155 - quarantine_body.velociraptor_id = get_velociraptor_id(quarantine_body.hostname)
156 - # Quarantine the host
157 - return quarantine_host(quarantine_body)
backend/app/connectors/velociraptor/schema/artifacts.py deleted
-107
@@ -1,107 +0,0 @@
1 -from enum import Enum
2 -from typing import Any
3 -from typing import Dict
4 -from typing import List
5 -from typing import Optional
6 -
7 -from pydantic import BaseModel
8 -from pydantic import Field
9 -
10 -
11 -class Artifacts(BaseModel):
12 - description: str = Field(..., description="Description of the artifact.")
13 - name: str = Field(..., description="Name of the artifact.")
14 -
15 -
16 -class ArtifactsResponse(BaseModel):
17 - message: str = Field(...)
18 - # make artifacts optional
19 - artifacts: Optional[List[Artifacts]]
20 - success: str = Field(...)
21 -
22 -
23 -class OSPrefixEnum(Enum):
24 - LINUX = "Linux."
25 - WINDOWS = "Windows."
26 - MACOS = "MacOS."
27 -
28 -
29 -class OSPrefixModel(BaseModel):
30 - os_name: Optional[str]
31 - os_prefix_mapping: Dict[str, str] = {
32 - "windows": "Windows",
33 - "linux": "Linux",
34 - "mac": "MacOS",
35 - "ubuntu": "Linux", # Add more mappings as needed
36 - }
37 -
38 - def get_os_prefix(self) -> Optional[str]:
39 - if self.os_name is None:
40 - return None
41 - return self._map_os_name_to_prefix()
42 -
43 - def _map_os_name_to_prefix(self) -> Optional[str]:
44 - os_name_lower = self.os_name.lower()
45 - for keyword, prefix in self.os_prefix_mapping.items():
46 - if keyword in os_name_lower:
47 - return prefix
48 - return None
49 -
50 -
51 -class OperationEnum(str, Enum):
52 - collect_artifact = "collect_artifact"
53 - run_command = "run_command"
54 - quarantine = "quarantine"
55 -
56 -
57 -class ActionEnum(str, Enum):
58 - quarantine = "quarantine"
59 - remove_quarantine = "remove_quarantine"
60 -
61 -
62 -class CommandArtifactsEnum(str, Enum):
63 - windows_powershell = "Windows.System.PowerShell"
64 - windows_cmd = "Windows.System.CmdShell"
65 - linux_bash = "Linux.Sys.BashShell"
66 -
67 -
68 -class QuarantineArtifactsEnum(str, Enum):
69 - windows_quarantine = "Windows.Remediation.Quarantine"
70 - linux_quarantine = "Linux.Remediation.Quarantine"
71 -
72 -
73 -class BaseBody(BaseModel):
74 - hostname: str = Field(..., description="Name of the client")
75 - velociraptor_id: Optional[str] = Field(None, description="Client ID of the client")
76 -
77 -
78 -class CollectArtifactBody(BaseBody):
79 - artifact_name: Optional[str] = Field(None, description="Name of the artifact for collection or command running")
80 -
81 -
82 -class RunCommandBody(BaseBody):
83 - command: Optional[str] = Field(None, description="Command to run")
84 - artifact_name: CommandArtifactsEnum = Field(None, description="Name of the artifact for command running")
85 -
86 -
87 -class QuarantineBody(BaseBody):
88 - action: ActionEnum = Field(..., description="Action to perform")
89 - artifact_name: QuarantineArtifactsEnum = Field(None, description="Name of the artifact for quarantine or removal of quarantine")
90 -
91 -
92 -class BaseResponse(BaseModel):
93 - message: str = Field(...)
94 - success: bool = Field(...) # Changed from str to bool based on your sample data
95 - results: Optional[List[Dict[str, Any]]] = Field(None, description="Results of the operation")
96 -
97 -
98 -class CollectArtifactResponse(BaseResponse):
99 - pass # If you have additional fields, you can define them here
100 -
101 -
102 -class RunCommandResponse(BaseResponse):
103 - pass # If you have additional fields, you can define them here
104 -
105 -
106 -class QuarantineResponse(BaseResponse):
107 - pass # If you have additional fields, you can define them here
backend/app/connectors/velociraptor/services/artifacts.py deleted
-237
@@ -1,237 +0,0 @@
1 -from fastapi import HTTPException
2 -from loguru import logger
3 -
4 -from app.connectors.velociraptor.schema.artifacts import Artifacts
5 -from app.connectors.velociraptor.schema.artifacts import ArtifactsResponse
6 -from app.connectors.velociraptor.schema.artifacts import CollectArtifactBody
7 -from app.connectors.velociraptor.schema.artifacts import CollectArtifactResponse
8 -from app.connectors.velociraptor.schema.artifacts import QuarantineBody
9 -from app.connectors.velociraptor.schema.artifacts import QuarantineResponse
10 -from app.connectors.velociraptor.schema.artifacts import RunCommandBody
11 -from app.connectors.velociraptor.schema.artifacts import RunCommandResponse
12 -from app.connectors.velociraptor.utils.universal import UniversalService
13 -
14 -universal_service = UniversalService()
15 -
16 -
17 -def create_query(query: str) -> str:
18 - """
19 - Create a query string.
20 -
21 - Args:
22 - query (str): The query to be executed.
23 -
24 - Returns:
25 - str: The created query string.
26 - """
27 - return query
28 -
29 -
30 -def get_artifact_key(analyzer_body: CollectArtifactBody) -> str:
31 - """
32 - Construct the artifact key.
33 -
34 - Args:
35 - client_id (str): The ID of the client.
36 - artifact (str): The name of the artifact.
37 - command (str): The command that was run, if applicable.
38 - quarantined (bool): Whether the client is quarantined or not.
39 -
40 - Returns:
41 - str: The constructed artifact key.
42 - """
43 - action = getattr(analyzer_body, "action", None)
44 - command = getattr(analyzer_body, "command", None)
45 -
46 - if action == "quarantine":
47 - return f'collect_client(client_id="{analyzer_body.velociraptor_id}", artifacts=["{analyzer_body.artifact_name}"], spec=dict(`{analyzer_body.artifact_name}`=dict()))'
48 - elif action == "remove_quarantine":
49 - return f'collect_client(client_id="{analyzer_body.velociraptor_id}", artifacts=["{analyzer_body.artifact_name}"], spec=dict(`{analyzer_body.artifact_name}`=dict(`RemovePolicy`="Y")))'
50 - elif command is not None:
51 - return f"collect_client(client_id='{analyzer_body.velociraptor_id}', urgent=true, artifacts=['{analyzer_body.artifact_name}'], env=dict(Command='{analyzer_body.command}'))"
52 - else:
53 - return f"collect_client(client_id='{analyzer_body.velociraptor_id}', artifacts=['{analyzer_body.artifact_name}'])"
54 -
55 -
56 -def get_artifacts() -> ArtifactsResponse:
57 - """
58 - Get all artifacts from Velociraptor.
59 -
60 - Returns:
61 - ArtifactsResponse: A dictionary containing the artifacts.
62 - """
63 - logger.info("Fetching artifacts from Velociraptor")
64 - query = create_query("SELECT name,description FROM artifact_definitions()")
65 - all_artifacts = universal_service.execute_query(query)
66 - if all_artifacts["success"]:
67 - artifacts = [Artifacts(**artifact) for artifact in all_artifacts["results"]]
68 - return ArtifactsResponse(success=True, message="All artifacts retrieved", artifacts=artifacts)
69 - else:
70 - raise HTTPException(status_code=500, detail=f"Failed to get all artifacts: {all_artifacts['message']}")
71 -
72 -
73 -def run_artifact_collection(collect_artifact_body: CollectArtifactBody) -> CollectArtifactResponse:
74 - """
75 - Run an artifact collection on a client.
76 -
77 - Args:
78 - run_analyzer_body (RunAnalyzerBody): The body of the request.
79 -
80 - Returns:
81 - RunAnalyzerResponse: A dictionary containing the success status and a message.
82 - """
83 - try:
84 - query = create_query(
85 - f"SELECT collect_client(client_id='{collect_artifact_body.velociraptor_id}', artifacts=['{collect_artifact_body.artifact_name}']) FROM scope()",
86 - )
87 - flow = universal_service.execute_query(query)
88 - logger.info(f"Successfully ran artifact collection on {flow}")
89 -
90 - artifact_key = get_artifact_key(analyzer_body=collect_artifact_body)
91 -
92 - flow_id = flow["results"][0][artifact_key]["flow_id"]
93 - logger.info(f"Extracted flow_id: {flow_id}")
94 -
95 - completed = universal_service.watch_flow_completion(flow_id)
96 - logger.info(f"Successfully watched flow completion on {completed}")
97 -
98 - results = universal_service.read_collection_results(
99 - client_id=collect_artifact_body.velociraptor_id,
100 - flow_id=flow_id,
101 - artifact=collect_artifact_body.artifact_name,
102 - )
103 -
104 - logger.info(f"Successfully read collection results on {results}")
105 -
106 - return CollectArtifactResponse(success=results["success"], message=results["message"], results=results["results"])
107 - except Exception as err:
108 - logger.error(f"Failed to run artifact collection on {collect_artifact_body}: {err}")
109 - raise HTTPException(status_code=500, detail=f"Failed to run artifact collection on {collect_artifact_body}: {err}")
110 -
111 -
112 -def run_remote_command(run_command_body: RunCommandBody) -> RunCommandResponse:
113 - """
114 - Run a remote command on a client.
115 -
116 - Args:
117 - run_analyzer_body (RunAnalyzerBody): The body of the request.
118 -
119 - Returns:
120 - RunAnalyzerResponse: A dictionary containing the success status and a message.
121 - """
122 - try:
123 - run_command_body.artifact_name = run_command_body.artifact_name.value
124 - logger.info(f"Running remote command on {run_command_body}")
125 - query = create_query(
126 - f"SELECT collect_client(client_id='{run_command_body.velociraptor_id}', urgent=true, artifacts=['{run_command_body.artifact_name}'], env=dict(Command='{run_command_body.command}')) "
127 - "FROM scope()",
128 - )
129 - flow = universal_service.execute_query(query)
130 - logger.info(f"Successfully ran artifact collection on {flow}")
131 -
132 - artifact_key = get_artifact_key(analyzer_body=run_command_body)
133 -
134 - flow_id = flow["results"][0][artifact_key]["flow_id"]
135 - logger.info(f"Extracted flow_id: {flow_id}")
136 -
137 - completed = universal_service.watch_flow_completion(flow_id)
138 - logger.info(f"Successfully watched flow completion on {completed}")
139 -
140 - results = universal_service.read_collection_results(
141 - client_id=run_command_body.velociraptor_id,
142 - flow_id=flow_id,
143 - artifact=run_command_body.artifact_name,
144 - )
145 -
146 - logger.info(f"Successfully read collection results on {results}")
147 -
148 - return RunCommandResponse(success=results["success"], message=results["message"], results=results["results"])
149 - except Exception as err:
150 - logger.error(f"Failed to run artifact collection on {run_command_body}: {err}")
151 - raise HTTPException(status_code=500, detail=f"Failed to run artifact collection on {run_command_body}: {err}")
152 -
153 -
154 -def quarantine_host(quarantine_body: QuarantineBody) -> QuarantineResponse:
155 - """
156 - Quarantine a host.
157 -
158 - Args:
159 - quarantine_body (QuarantineBody): The body of the request.
160 -
161 - Returns:
162 - QuarantineResponse: A dictionary containing the success status and a message.
163 - """
164 - try:
165 - quarantine_body.artifact_name = quarantine_body.artifact_name.value
166 - quarantine_body.action = quarantine_body.action.value
167 - if quarantine_body.action == "quarantine":
168 - query = create_query(
169 - f'SELECT collect_client(client_id="{quarantine_body.velociraptor_id}", artifacts=["{quarantine_body.artifact_name}"], spec=dict(`{quarantine_body.artifact_name}`=dict())) FROM scope()',
170 - )
171 - else:
172 - query = create_query(
173 - f'SELECT collect_client(client_id="{quarantine_body.velociraptor_id}", artifacts=["{quarantine_body.artifact_name}"], spec=dict(`{quarantine_body.artifact_name}`=dict(`RemovePolicy`="Y"))) FROM scope()',
174 - )
175 - flow = universal_service.execute_query(query)
176 - logger.info(f"Successfully ran artifact collection on {flow}")
177 -
178 - artifact_key = get_artifact_key(analyzer_body=quarantine_body)
179 -
180 - flow_id = flow["results"][0][artifact_key]["flow_id"]
181 - logger.info(f"Extracted flow_id: {flow_id}")
182 -
183 - completed = universal_service.watch_flow_completion(flow_id)
184 - logger.info(f"Successfully watched flow completion on {completed}")
185 -
186 - results = universal_service.read_collection_results(
187 - client_id=quarantine_body.velociraptor_id,
188 - flow_id=flow_id,
189 - artifact=quarantine_body.artifact_name,
190 - )
191 -
192 - logger.info(f"Successfully read collection results on {results}")
193 -
194 - return QuarantineResponse(success=results["success"], message=results["message"], results=results["results"])
195 - except Exception as err:
196 - logger.error(f"Failed to run artifact collection on {quarantine_body}: {err}")
197 - raise HTTPException(status_code=500, detail=f"Failed to run artifact collection on {quarantine_body}: {err}")
198 -
199 -
200 -######################## KEEP
201 -class ArtifactsService:
202 - def delete_client(self, client_id: str) -> dict:
203 - """
204 - Delete a client from Velociraptor.
205 -
206 - Args:
207 - client_id (str): The ID of the client.
208 -
209 - Returns:
210 - dict: A dictionary with the success status and a message.
211 - """
212 - try:
213 - query = self._create_query(
214 - f"SELECT collect_client(client_id='server', artifacts=['Server.Utils.DeleteClient'], env=dict(ClientIdList='{client_id}',ReallyDoIt='Y')) "
215 - "FROM scope()",
216 - )
217 -
218 - flow = self.universal_service.execute_query(query)
219 - logger.info(f"Successfully ran artifact collection on {flow}")
220 -
221 - # artifact_key = f"collect_client(client_id='server', artifacts=['Server.Utils.DeleteClient'], env=dict(ClientIdList='{client_id}',ReallyDoIt='Y'))"
222 - flow_id = flow["results"][0][query]["flow_id"]
223 - logger.info(f"Extracted flow_id: {flow_id}")
224 -
225 - completed = self.universal_service.watch_flow_completion(flow_id)
226 - logger.info(f"Successfully watched flow completion on {completed}")
227 -
228 - return {
229 - "message": f"Successfully deleted client {client_id}",
230 - "success": True,
231 - }
232 - except Exception as err:
233 - logger.error(f"Failed to delete client {client_id}: {err}")
234 - return {
235 - "message": f"Failed to delete client {client_id}",
236 - "success": False,
237 - }
backend/app/connectors/velociraptor/utils/universal.py deleted
-283
@@ -1,283 +0,0 @@
1 -import json
2 -from datetime import datetime
3 -from typing import Any
4 -from typing import Dict
5 -
6 -import grpc
7 -import pyvelociraptor
8 -from loguru import logger
9 -from pyvelociraptor import api_pb2
10 -from pyvelociraptor import api_pb2_grpc
11 -
12 -from app.connectors.utils import get_connector_info_from_db
13 -
14 -
15 -def verify_velociraptor_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
16 - """
17 - Verifies the connection to Velociraptor service.
18 -
19 - Returns:
20 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
21 - """
22 - try:
23 - connector_api_key = attributes["connector_api_key"]
24 -
25 - with open(connector_api_key, "r") as f:
26 - f.read()
27 -
28 - try:
29 - config = pyvelociraptor.LoadConfigFile(connector_api_key)
30 - creds = grpc.ssl_channel_credentials(
31 - root_certificates=config["ca_certificate"].encode("utf8"),
32 - private_key=config["client_private_key"].encode("utf8"),
33 - certificate_chain=config["client_cert"].encode("utf8"),
34 - )
35 -
36 - options = (("grpc.ssl_target_name_override", "VelociraptorServer"),)
37 -
38 - with grpc.secure_channel(
39 - config["api_connection_string"],
40 - creds,
41 - options,
42 - ) as channel:
43 - stub = api_pb2_grpc.APIStub(channel)
44 - client_query = "SELECT * FROM info()"
45 -
46 - client_request = api_pb2.VQLCollectorArgs(
47 - max_wait=60,
48 - Query=[
49 - api_pb2.VQLRequest(
50 - Name="ClientQuery",
51 - VQL=client_query,
52 - ),
53 - ],
54 - )
55 -
56 - r = []
57 - for response in stub.Query(client_request):
58 - if response.Response:
59 - r = r + json.loads(response.Response)
60 - return {"connectionSuccessful": True, "message": "Connection to Velociraptor successful"}
61 - except Exception as e:
62 - logger.error(f"Failed to verify connection to Velociraptor: {e}")
63 - return {"connectionSuccessful": False, "message": f"Failed to verify connection to Velociraptor: {e}"}
64 - except Exception as e:
65 - logger.error(f"Failed to get connector_api_key from the database: {e}")
66 - return {"connectionSuccessful": False, "message": f"Failed to get connector_api_key from the database: {e}"}
67 -
68 -
69 -def verify_velociraptor_connection(connector_name: str) -> str:
70 - """
71 - Verifies the connection to Velociraptor service.
72 - """
73 - logger.info(f"Verifying the Velociraptor connection for connector: {connector_name}")
74 - attributes = get_connector_info_from_db(connector_name)
75 - if attributes is None:
76 - logger.error("No Velociraptor connector found in the database")
77 - return None
78 - return verify_velociraptor_credentials(attributes)
79 -
80 -
81 -class UniversalService:
82 - """
83 - A service class that encapsulates the logic for polling messages from Velociraptor.
84 - """
85 -
86 - def __init__(self) -> None:
87 - self.setup_velociraptor_connector("Velociraptor")
88 - self.setup_grpc_channel_and_stub()
89 -
90 - def setup_velociraptor_connector(self, connector_name: str):
91 - """
92 - Collects the details of the Velociraptor connector and sets them up.
93 -
94 - Args:
95 - connector_name (str): The name of the Velociraptor connector.
96 - """
97 - attributes = get_connector_info_from_db(connector_name)
98 - if attributes is None:
99 - logger.error("No Velociraptor connector found in the database")
100 - return None
101 - self.connector_api_key = attributes["connector_api_key"]
102 - self.config = pyvelociraptor.LoadConfigFile(self.connector_api_key)
103 -
104 - def setup_grpc_channel_and_stub(self):
105 - """
106 - Sets up the gRPC channel and stub for Velociraptor.
107 - """
108 - creds = grpc.ssl_channel_credentials(
109 - root_certificates=self.config["ca_certificate"].encode("utf8"),
110 - private_key=self.config["client_private_key"].encode("utf8"),
111 - certificate_chain=self.config["client_cert"].encode("utf8"),
112 - )
113 - options = (("grpc.ssl_target_name_override", "VelociraptorServer"),)
114 - self.channel = grpc.secure_channel(
115 - self.config["api_connection_string"],
116 - creds,
117 - options,
118 - )
119 - self.stub = api_pb2_grpc.APIStub(self.channel)
120 -
121 - def create_vql_request(self, vql: str):
122 - """
123 - Creates a VQLCollectorArgs object with given VQL query.
124 -
125 - Args:
126 - vql (str): The VQL query.
127 -
128 - Returns:
129 - VQLCollectorArgs: The VQLCollectorArgs object with given VQL query.
130 - """
131 - return api_pb2.VQLCollectorArgs(
132 - max_wait=1,
133 - Query=[
134 - api_pb2.VQLRequest(
135 - Name="VQLRequest",
136 - VQL=vql,
137 - ),
138 - ],
139 - )
140 -
141 - def execute_query(self, vql: str):
142 - """
143 - Executes a VQL query and returns the results.
144 -
145 - Args:
146 - vql (str): The VQL query to be executed.
147 -
148 - Returns:
149 - dict: A dictionary with the success status, a message, and potentially the results.
150 - """
151 - client_request = self.create_vql_request(vql)
152 - try:
153 - results = []
154 - for response in self.stub.Query(client_request):
155 - if response.Response:
156 - results += json.loads(response.Response)
157 - return {
158 - "success": True,
159 - "message": "Successfully executed query",
160 - "results": results,
161 - }
162 - except Exception as e:
163 - logger.error(f"Failed to execute query: {e}")
164 - return {
165 - "success": False,
166 - "message": f"Failed to execute query: {e}",
167 - }
168 -
169 - def watch_flow_completion(self, flow_id: str):
170 - """
171 - Watch for the completion of a flow.
172 -
173 - Args:
174 - flow_id (str): The ID of the flow.
175 -
176 - Returns:
177 - dict: A dictionary with the success status and a message.
178 - """
179 - vql = f"SELECT * FROM watch_monitoring(artifact='System.Flow.Completion') WHERE FlowId='{flow_id}' LIMIT 1"
180 - return self.execute_query(vql)
181 -
182 - def read_collection_results(
183 - self,
184 - client_id: str,
185 - flow_id: str,
186 - artifact: str = "Generic.Client.Info/BasicInformation",
187 - ):
188 - """
189 - Read the results of a collection.
190 -
191 - Args:
192 - client_id (str): The client ID.
193 - flow_id (str): The ID of the flow.
194 - artifact (str, optional): The artifact. Defaults to 'Generic.Client.Info/BasicInformation'.
195 -
196 - Returns:
197 - dict: A dictionary with the success status, a message, and potentially the results.
198 - """
199 - vql = f"SELECT * FROM source(client_id='{client_id}', flow_id='{flow_id}', artifact='{artifact}')"
200 - return self.execute_query(vql)
201 -
202 - def get_client_id(self, client_name: str):
203 - """
204 - Get the client_id associated with a given client_name.
205 -
206 - Args:
207 - client_name (str): The asset name to search for.
208 -
209 - Returns:
210 - dict: A dictionary with the success status, a message, and potentially the client_id.
211 - """
212 - # Formulate queries
213 - try:
214 - vql_client_id = f"select client_id,os_info from clients(search='host:{client_name}')"
215 - vql_last_seen_at = f"select last_seen_at from clients(search='host:{client_name}')"
216 -
217 - # Get the last seen timestamp
218 - last_seen_at = self._get_last_seen_timestamp(vql_last_seen_at)
219 -
220 - # if last_seen_at is longer than 30 seconds from now, return False
221 - if self._is_offline(last_seen_at):
222 - return {
223 - "success": False,
224 - "message": f"{client_name} has not been seen in the last 30 seconds and "
225 - "may not be online with the Velociraptor server.",
226 - "results": [{"client_id": None}],
227 - }
228 -
229 - return self.execute_query(vql_client_id)
230 - except Exception as e:
231 - return {
232 - "success": False,
233 - "message": f"Failed to get Client ID for {client_name}: {e}",
234 - "results": [{"client_id": None}],
235 - }
236 -
237 - def _get_last_seen_timestamp(self, vql: str):
238 - """
239 - Executes the VQL query and returns the last_seen_at timestamp.
240 -
241 - Args:
242 - vql (str): The VQL query.
243 -
244 - Returns:
245 - float: The last_seen_at timestamp.
246 - """
247 - return self.execute_query(vql)["results"][0]["last_seen_at"]
248 -
249 - def _get_client_version(self, vql: str):
250 - """
251 - Executes the VQL query and returns the `agent_information``version` field
252 -
253 - Args:
254 - vql (str): The VQL query.
255 -
256 - Returns:
257 - str: The client version.
258 - """
259 - return self.execute_query(vql)["results"][0]["agent_information"]["version"]
260 -
261 - def _get_server_version(self, vql: str):
262 - """
263 - Executes the VQL query and returns the velociraptor server version.
264 -
265 - Args:
266 - vql (str): The VQL query.
267 -
268 - Returns:
269 - str: The server version.
270 - """
271 - return self.execute_query(vql)["results"][0]["version"]["version"]
272 -
273 - def _is_offline(self, last_seen_at: float):
274 - """
275 - Determines if the client is offline based on the last_seen_at timestamp.
276 -
277 - Args:
278 - last_seen_at (float): The last_seen_at timestamp.
279 -
280 - Returns:
281 - bool: True if the client is offline, False otherwise.
282 - """
283 - return (datetime.now() - datetime.fromtimestamp(last_seen_at / 1000000)).total_seconds() > 30
backend/app/connectors/wazuh_indexer/models/db.py
backend/app/connectors/wazuh_indexer/routes/alerts.py deleted
-97
@@ -1,97 +0,0 @@
1 -from typing import List
2 -
3 -from fastapi import APIRouter
4 -from fastapi import Depends
5 -from fastapi import HTTPException
6 -from loguru import logger
7 -
8 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByHostResponse
9 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByRulePerHostResponse
10 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByRuleResponse
11 -from app.connectors.wazuh_indexer.schema.alerts import AlertsSearchBody
12 -from app.connectors.wazuh_indexer.schema.alerts import AlertsSearchResponse
13 -from app.connectors.wazuh_indexer.schema.alerts import HostAlertsSearchBody
14 -from app.connectors.wazuh_indexer.schema.alerts import HostAlertsSearchResponse
15 -from app.connectors.wazuh_indexer.schema.alerts import IndexAlertsSearchBody
16 -from app.connectors.wazuh_indexer.schema.alerts import IndexAlertsSearchResponse
17 -from app.connectors.wazuh_indexer.services.alerts import get_alerts
18 -from app.connectors.wazuh_indexer.services.alerts import get_alerts_by_host
19 -from app.connectors.wazuh_indexer.services.alerts import get_alerts_by_rule
20 -from app.connectors.wazuh_indexer.services.alerts import get_alerts_by_rule_per_host
21 -from app.connectors.wazuh_indexer.services.alerts import get_host_alerts
22 -from app.connectors.wazuh_indexer.services.alerts import get_index_alerts
23 -from app.connectors.wazuh_indexer.utils.universal import collect_indices
24 -
25 -# App specific imports
26 -
27 -
28 -wazuh_indexer_alerts_router = APIRouter()
29 -
30 -
31 -def get_index_names() -> List[str]:
32 - indices = collect_indices()
33 - return indices.indices_list
34 -
35 -
36 -def verify_index_name(index_alerts_search_body: IndexAlertsSearchBody) -> IndexAlertsSearchBody:
37 - # Remove any extra spaces from index_name
38 - index_alerts_search_body.index_name = index_alerts_search_body.index_name.strip()
39 -
40 - managed_index_names = get_index_names()
41 - if index_alerts_search_body.index_name not in managed_index_names:
42 - raise HTTPException(
43 - status_code=400,
44 - detail=f"Index name '{index_alerts_search_body.index_name}' is not managed by Wazuh Indexer or no longer exists.",
45 - )
46 - return index_alerts_search_body
47 -
48 -
49 -@wazuh_indexer_alerts_router.post("", response_model=AlertsSearchResponse, description="Get all alerts")
50 -async def get_all_alerts(alerts_search_body: AlertsSearchBody) -> AlertsSearchResponse:
51 - logger.info("Fetching all alerts")
52 - return get_alerts(alerts_search_body)
53 -
54 -
55 -@wazuh_indexer_alerts_router.post("/host", response_model=HostAlertsSearchResponse, description="Get all alerts for a host")
56 -async def get_all_alerts_for_host(host_alerts_search_body: HostAlertsSearchBody) -> HostAlertsSearchResponse:
57 - logger.info(f"Fetching all alerts for host {host_alerts_search_body.agent_name}")
58 - return get_host_alerts(host_alerts_search_body)
59 -
60 -
61 -@wazuh_indexer_alerts_router.post("/index", response_model=IndexAlertsSearchResponse, description="Get all alerts for an index")
62 -async def get_all_alerts_for_index(
63 - index_alerts_search_body: IndexAlertsSearchBody = Depends(verify_index_name),
64 -) -> IndexAlertsSearchResponse:
65 - logger.info(f"Fetching all alerts for index {index_alerts_search_body.index_name}")
66 - return get_index_alerts(index_alerts_search_body)
67 -
68 -
69 -@wazuh_indexer_alerts_router.post("/hosts/all", response_model=AlertsByHostResponse, description="Get number of all alerts for all hosts")
70 -async def get_all_alerts_by_host(alerts_search_body: AlertsSearchBody) -> AlertsByHostResponse:
71 - logger.info("Fetching number of all alerts for all hosts")
72 - return get_alerts_by_host(alerts_search_body)
73 -
74 -
75 -@wazuh_indexer_alerts_router.post("/rules/all", response_model=AlertsByRuleResponse, description="Get number of all alerts for all rules")
76 -async def get_all_alerts_by_rule(alerts_search_body: AlertsSearchBody) -> AlertsByRuleResponse:
77 - logger.info("Fetching number of all alerts for all rules")
78 - return get_alerts_by_rule(alerts_search_body)
79 -
80 -
81 -@wazuh_indexer_alerts_router.post(
82 - "/rules/hosts/all",
83 - response_model=AlertsByRulePerHostResponse,
84 - description="Get number of all alerts for all rules per host",
85 -)
86 -async def get_all_alerts_by_rule_per_host(alerts_search_body: AlertsSearchBody) -> AlertsByRulePerHostResponse:
87 - """
88 - Get number of all alerts for all rules per host
89 -
90 - Args:
91 - alerts_search_body (AlertsSearchBody): _description_
92 -
93 - Returns:
94 - AlertsByRulePerHostResponse: _description_
95 - """
96 - logger.info("Fetching number of all alerts for all rules per host")
97 - return get_alerts_by_rule_per_host(alerts_search_body)
backend/app/connectors/wazuh_indexer/routes/monitoring.py deleted
-97
@@ -1,97 +0,0 @@
1 -from typing import Union
2 -
3 -from fastapi import APIRouter
4 -from fastapi import HTTPException
5 -
6 -from app.connectors.wazuh_indexer.schema.monitoring import ClusterHealthResponse
7 -from app.connectors.wazuh_indexer.schema.monitoring import IndicesStatsResponse
8 -from app.connectors.wazuh_indexer.schema.monitoring import NodeAllocationResponse
9 -from app.connectors.wazuh_indexer.schema.monitoring import ShardsResponse
10 -
11 -# from app.connectors.wazuh_indexer.schema import WazuhIndexerResponse, WazuhIndexerListResponse
12 -from app.connectors.wazuh_indexer.services.monitoring import cluster_healthcheck
13 -from app.connectors.wazuh_indexer.services.monitoring import indices_stats
14 -from app.connectors.wazuh_indexer.services.monitoring import node_allocation
15 -from app.connectors.wazuh_indexer.services.monitoring import shards
16 -
17 -wazuh_indexer_router = APIRouter()
18 -
19 -
20 -@wazuh_indexer_router.get("/health", response_model=ClusterHealthResponse, description="Fetch Wazuh Indexer cluster health")
21 -async def get_cluster_health() -> Union[ClusterHealthResponse, HTTPException]:
22 - """
23 - Fetch Wazuh Indexer cluster health.
24 -
25 - This endpoint retrieves the cluster health of the Wazuh Indexer service.
26 -
27 - Returns:
28 - ElasticsearchResponse: A Pydantic model representing the cluster health of the Wazuh Indexer service.
29 -
30 - Raises:
31 - HTTPException: An exception with a 500 status code is raised if the cluster health cannot be retrieved.
32 - """
33 - cluster_health = cluster_healthcheck()
34 - if cluster_health is not None:
35 - return cluster_health
36 - else:
37 - raise HTTPException(status_code=500, detail="Failed to retrieve cluster health.")
38 -
39 -
40 -@wazuh_indexer_router.get("/allocation", response_model=NodeAllocationResponse, description="Fetch Wazuh Indexer node allocation")
41 -async def get_node_allocation() -> Union[NodeAllocationResponse, HTTPException]:
42 - """
43 - Fetch Wazuh Indexer node allocation.
44 -
45 - This endpoint retrieves the node allocation of the Wazuh Indexer service.
46 -
47 - Returns:
48 - ElasticsearchResponse: A Pydantic model representing the node allocation of the Wazuh Indexer service.
49 -
50 - Raises:
51 - HTTPException: An exception with a 500 status code is raised if the node allocation cannot be retrieved.
52 - """
53 - node_allocation_response = node_allocation()
54 - if node_allocation_response is not None:
55 - return node_allocation_response
56 - else:
57 - raise HTTPException(status_code=500, detail="Failed to retrieve node allocation.")
58 -
59 -
60 -@wazuh_indexer_router.get("/indices", response_model=IndicesStatsResponse, description="Fetch Wazuh Indexer indices stats")
61 -async def get_indices_stats() -> Union[IndicesStatsResponse, HTTPException]:
62 - """
63 - Fetch Wazuh Indexer indices stats.
64 -
65 - This endpoint retrieves the indices stats of the Wazuh Indexer service.
66 -
67 - Returns:
68 - ElasticsearchResponse: A Pydantic model representing the indices stats of the Wazuh Indexer service.
69 -
70 - Raises:
71 - HTTPException: An exception with a 500 status code is raised if the indices stats cannot be retrieved.
72 - """
73 - indices_stats_response = indices_stats()
74 - if indices_stats_response is not None:
75 - return indices_stats_response
76 - else:
77 - raise HTTPException(status_code=500, detail="Failed to retrieve indices stats.")
78 -
79 -
80 -@wazuh_indexer_router.get("/shards", response_model=ShardsResponse, description="Fetch Wazuh Indexer shards")
81 -async def get_shards() -> Union[ShardsResponse, HTTPException]:
82 - """
83 - Fetch Wazuh Indexer shards.
84 -
85 - This endpoint retrieves the shards of the Wazuh Indexer service.
86 -
87 - Returns:
88 - ElasticsearchResponse: A Pydantic model representing the shards of the Wazuh Indexer service.
89 -
90 - Raises:
91 - HTTPException: An exception with a 500 status code is raised if the shards cannot be retrieved.
92 - """
93 - shards_response = shards()
94 - if shards_response is not None:
95 - return shards_response
96 - else:
97 - raise HTTPException(status_code=500, detail="Failed to retrieve shards.")
backend/app/connectors/wazuh_indexer/schema/alerts.py deleted
-99
@@ -1,99 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import List
4 -from typing import Optional
5 -
6 -from pydantic import BaseModel
7 -from pydantic import Field
8 -from pydantic import validator
9 -
10 -
11 -class Alert(BaseModel):
12 - index_name: str
13 - total_alerts: int
14 - alerts: Optional[List[Dict[str, Any]]] = Field([], description="The alerts returned from the search.")
15 -
16 -
17 -class AlertsSearchBody(BaseModel):
18 - size: int = Field(10, description="The number of alerts to return.")
19 - timerange: str = Field("24h", description="The time range to search alerts in.")
20 - alert_field: str = Field("syslog_level", description="The field to search alerts in.")
21 - alert_value: str = Field("ALERT", description="The value to search alerts for.")
22 - timestamp_field: str = Field("timestamp_utc", description="The timestamp field to search alerts in.")
23 -
24 - @validator("timerange")
25 - def validate_timerange(cls, value):
26 - if value[-1] not in ("h", "d", "w", "m"):
27 - raise ValueError("Invalid timerange format. The string should end with either 'h', 'd', 'w', or 'm'.")
28 -
29 - # Optionally, you can check that the prefix is a number
30 - if not value[:-1].isdigit():
31 - raise ValueError("Invalid timerange format. The string should start with a number.")
32 -
33 - return value
34 -
35 -
36 -class AlertsSearchResponse(BaseModel):
37 - alerts_summary: List[Alert]
38 - success: bool
39 - message: str
40 -
41 -
42 -class CollectAlertsResponse(BaseModel):
43 - alerts: List[Dict[str, Any]]
44 - success: bool
45 - message: str
46 -
47 -
48 -class HostAlertsSearchBody(AlertsSearchBody):
49 - agent_name: str = Field(..., description="The name of the agent to search alerts for.")
50 -
51 -
52 -class HostAlertsSearchResponse(BaseModel):
53 - alerts_summary: List[Alert]
54 - success: bool
55 - message: str
56 -
57 -
58 -class IndexAlertsSearchBody(AlertsSearchBody):
59 - index_name: str = Field(..., description="The name of the index to search alerts for.")
60 -
61 -
62 -class IndexAlertsSearchResponse(BaseModel):
63 - alerts_summary: List[Alert]
64 - success: bool
65 - message: str
66 -
67 -
68 -class AlertsByHost(BaseModel):
69 - agent_name: str
70 - number_of_alerts: int
71 -
72 -
73 -class AlertsByHostResponse(BaseModel):
74 - alerts_by_host: List[AlertsByHost]
75 - success: bool
76 - message: str
77 -
78 -
79 -class AlertsByRule(BaseModel):
80 - rule: str
81 - number_of_alerts: int
82 -
83 -
84 -class AlertsByRuleResponse(BaseModel):
85 - alerts_by_rule: List[AlertsByRule]
86 - success: bool
87 - message: str
88 -
89 -
90 -class AlertsByRulePerHost(BaseModel):
91 - agent_name: str
92 - number_of_alerts: int
93 - rule: str
94 -
95 -
96 -class AlertsByRulePerHostResponse(BaseModel):
97 - alerts_by_rule_per_host: List[AlertsByRulePerHost]
98 - success: bool
99 - message: str
backend/app/connectors/wazuh_indexer/schema/indices.py deleted
-44
@@ -1,44 +0,0 @@
1 -from typing import Dict
2 -
3 -from pydantic import BaseModel
4 -from pydantic import Field
5 -
6 -
7 -class Indices(BaseModel):
8 - indices_list: list
9 - success: bool
10 - message: str
11 -
12 -
13 -class IndexConfigModel(BaseModel):
14 - SKIP_INDEX_NAMES: Dict[str, bool] = Field(
15 - default={
16 - "wazuh-statistics": True,
17 - "wazuh-monitoring": True,
18 - },
19 - description="A dictionary containing index names to be skipped and their skip status.",
20 - )
21 -
22 - def is_index_skipped(self, index_name: str) -> bool:
23 - """
24 - Checks whether the given index name should be skipped.
25 -
26 - Args:
27 - index_name (str): The name of the index to check.
28 -
29 - Returns:
30 - bool: True if the index should be skipped, False otherwise.
31 - """
32 - return any(index_name.startswith(skipped) for skipped in self.SKIP_INDEX_NAMES)
33 -
34 - def is_valid_index(self, index_name: str) -> bool:
35 - """
36 - Checks if the index name starts with "wazuh_" and is not in the SKIP_INDEX_NAMES list.
37 -
38 - Args:
39 - index_name (str): The name of the index to check.
40 -
41 - Returns:
42 - bool: True if the index is valid, False otherwise.
43 - """
44 - return index_name.startswith("wazuh") and not self.is_index_skipped(index_name)
backend/app/connectors/wazuh_indexer/schema/monitoring.py deleted
-74
@@ -1,74 +0,0 @@
1 -from typing import List
2 -from typing import Optional
3 -from typing import Union
4 -
5 -from pydantic import BaseModel
6 -from pydantic import Field
7 -
8 -
9 -class ClusterHealth(BaseModel):
10 - active_primary_shards: int
11 - active_shards: int
12 - active_shards_percent_as_number: Union[int, float]
13 - cluster_name: str
14 - delayed_unassigned_shards: int
15 - discovered_cluster_manager: bool
16 - discovered_master: bool
17 - initializing_shards: int
18 - number_of_data_nodes: int
19 - number_of_in_flight_fetch: int
20 - number_of_nodes: int
21 - number_of_pending_tasks: int
22 - relocating_shards: int
23 - status: str
24 - task_max_waiting_in_queue_millis: int
25 - timed_out: bool
26 - unassigned_shards: int
27 -
28 -
29 -class ClusterHealthResponse(BaseModel):
30 - cluster_health: Optional[ClusterHealth]
31 - message: str
32 - success: bool
33 -
34 -
35 -class NodeAllocation(BaseModel):
36 - disk_available: Optional[str] = Field(None, description="Disk available in bytes")
37 - disk_percent: Optional[str] = Field(None, description="Disk percent")
38 - disk_total: Optional[str] = Field(None, description="Disk total in bytes")
39 - disk_used: Optional[str] = Field(None, description="Disk used in bytes")
40 - node: str
41 -
42 -
43 -class NodeAllocationResponse(BaseModel):
44 - node_allocation: Optional[List[NodeAllocation]]
45 - message: str
46 - success: bool
47 -
48 -
49 -class IndicesStats(BaseModel):
50 - docs_count: str
51 - health: str
52 - index: str
53 - replica_count: str
54 - store_size: str
55 -
56 -
57 -class IndicesStatsResponse(BaseModel):
58 - indices_stats: Optional[List[IndicesStats]]
59 - message: str
60 - success: bool
61 -
62 -
63 -class Shards(BaseModel):
64 - index: str
65 - node: Optional[str] = Field(None, description="Node name")
66 - shard: int
67 - state: str
68 - size: Optional[str] = Field(None, description="Shard size in bytes")
69 -
70 -
71 -class ShardsResponse(BaseModel):
72 - shards: Optional[List[Shards]]
73 - message: str
74 - success: bool
backend/app/connectors/wazuh_indexer/services/alerts.py deleted
-163
@@ -1,163 +0,0 @@
1 -from typing import Dict
2 -from typing import List
3 -from typing import Optional
4 -from typing import Type
5 -
6 -from fastapi import HTTPException
7 -from loguru import logger
8 -
9 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByHost
10 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByHostResponse
11 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByRule
12 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByRulePerHost
13 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByRulePerHostResponse
14 -from app.connectors.wazuh_indexer.schema.alerts import AlertsByRuleResponse
15 -from app.connectors.wazuh_indexer.schema.alerts import AlertsSearchBody
16 -from app.connectors.wazuh_indexer.schema.alerts import AlertsSearchResponse
17 -from app.connectors.wazuh_indexer.schema.alerts import CollectAlertsResponse
18 -from app.connectors.wazuh_indexer.schema.alerts import HostAlertsSearchBody
19 -from app.connectors.wazuh_indexer.schema.alerts import HostAlertsSearchResponse
20 -from app.connectors.wazuh_indexer.schema.alerts import IndexAlertsSearchBody
21 -from app.connectors.wazuh_indexer.schema.alerts import IndexAlertsSearchResponse
22 -from app.connectors.wazuh_indexer.utils.universal import AlertsQueryBuilder
23 -from app.connectors.wazuh_indexer.utils.universal import collect_indices
24 -from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
25 -
26 -# def collect_and_aggregate_alerts(field_name: str, search_body: AlertsSearchBody) -> Dict[str, int]:
27 -# indices = collect_indices()
28 -# aggregated_alerts_dict = {}
29 -
30 -# for index_name in indices.indices_list:
31 -# try:
32 -# alerts_response = collect_alerts_generic(index_name, body=search_body)
33 -# if alerts_response.success:
34 -# for alert in alerts_response.alerts:
35 -# field_value = alert["_source"][field_name]
36 -# aggregated_alerts_dict[field_value] = aggregated_alerts_dict.get(field_value, 0) + 1
37 -# except HTTPException as e:
38 -# logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
39 -
40 -# return aggregated_alerts_dict
41 -
42 -
43 -def collect_and_aggregate_alerts(field_names: List[str], search_body: AlertsSearchBody) -> Dict[str, int]:
44 - indices = collect_indices()
45 - aggregated_alerts_dict = {}
46 -
47 - for index_name in indices.indices_list:
48 - try:
49 - alerts_response = collect_alerts_generic(index_name, body=search_body)
50 - if alerts_response.success:
51 - for alert in alerts_response.alerts:
52 - composite_key = tuple(alert["_source"][field] for field in field_names)
53 - aggregated_alerts_dict[composite_key] = aggregated_alerts_dict.get(composite_key, 0) + 1
54 - except HTTPException as e:
55 - logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
56 -
57 - return aggregated_alerts_dict
58 -
59 -
60 -def collect_alerts_generic(index_name: str, body: AlertsSearchBody, is_host_specific: bool = False) -> CollectAlertsResponse:
61 - es_client = create_wazuh_indexer_client("Wazuh-Indexer")
62 - query_builder = AlertsQueryBuilder()
63 - query_builder.add_time_range(timerange=body.timerange, timestamp_field=body.timestamp_field)
64 - query_builder.add_matches(matches=[(body.alert_field, body.alert_value)])
65 - query_builder.add_sort(body.timestamp_field)
66 -
67 - if is_host_specific:
68 - query_builder.add_match_phrase(matches=[("agent_name", body.agent_name)])
69 -
70 - query = query_builder.build()
71 -
72 - try:
73 - alerts = es_client.search(index=index_name, body=query, size=body.size)
74 - logger.info(f"Alerts collected: {alerts}")
75 - alerts_list = [alert for alert in alerts["hits"]["hits"]]
76 - logger.info(f"Alerts collected: {alerts_list}")
77 - return CollectAlertsResponse(alerts=alerts_list, success=True, message="Alerts collected successfully")
78 - except Exception as e:
79 - logger.debug(f"Failed to collect alerts: {e}")
80 - return CollectAlertsResponse(alerts=[], success=False, message=f"Failed to collect alerts: {e}")
81 -
82 -
83 -def get_alerts_generic(search_body: Type[AlertsSearchBody], is_host_specific: bool = False, index_name: Optional[str] = None):
84 - logger.info(f"Collecting Wazuh Indexer alerts for host {search_body.agent_name if is_host_specific else ''}")
85 - alerts_summary = []
86 - indices = collect_indices()
87 - index_list = [index_name] if index_name else indices.indices_list # Use the provided index_name or get all indices
88 -
89 - for index_name in index_list:
90 - try:
91 - alerts = collect_alerts_generic(index_name, body=search_body, is_host_specific=is_host_specific)
92 - if alerts.success and len(alerts.alerts) > 0:
93 - alerts_summary.append(
94 - {
95 - "index_name": index_name,
96 - "total_alerts": len(alerts.alerts),
97 - "alerts": alerts.alerts,
98 - },
99 - )
100 - except HTTPException as e:
101 - logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
102 -
103 - if len(alerts_summary) == 0:
104 - message = "No alerts found"
105 - else:
106 - message = f"Succesfully collected top {search_body.size} alerts for each index"
107 -
108 - return {"alerts_summary": alerts_summary, "success": len(alerts_summary) > 0, "message": message}
109 -
110 -
111 -def get_alerts(search_body: AlertsSearchBody) -> AlertsSearchResponse:
112 - result = get_alerts_generic(search_body)
113 - return AlertsSearchResponse(**result)
114 -
115 -
116 -def get_host_alerts(search_body: HostAlertsSearchBody) -> HostAlertsSearchResponse:
117 - result = get_alerts_generic(search_body, is_host_specific=True)
118 - return HostAlertsSearchResponse(**result)
119 -
120 -
121 -def get_index_alerts(search_body: IndexAlertsSearchBody) -> IndexAlertsSearchResponse:
122 - result = get_alerts_generic(search_body, index_name=search_body.index_name)
123 - return IndexAlertsSearchResponse(**result)
124 -
125 -
126 -def get_alerts_by_host(search_body: AlertsSearchBody) -> AlertsByHostResponse:
127 - aggregated_by_host = collect_and_aggregate_alerts(["agent_name"], search_body)
128 - alerts_by_host_list: List[AlertsByHost] = [
129 - AlertsByHost(agent_name=host[0], number_of_alerts=count) # host[0] because host is now a tuple
130 - for host, count in aggregated_by_host.items()
131 - ]
132 - return AlertsByHostResponse(
133 - alerts_by_host=alerts_by_host_list,
134 - success=bool(alerts_by_host_list),
135 - message="Successfully collected alerts by host",
136 - )
137 -
138 -
139 -def get_alerts_by_rule(search_body: AlertsSearchBody) -> AlertsByRuleResponse:
140 - aggregated_by_rule = collect_and_aggregate_alerts(["rule_description"], search_body)
141 - alerts_by_rule_list: List[AlertsByRule] = [
142 - AlertsByRule(rule=rule[0], number_of_alerts=count) # rule[0] because rule is now a tuple
143 - for rule, count in aggregated_by_rule.items()
144 - ]
145 - return AlertsByRuleResponse(
146 - alerts_by_rule=alerts_by_rule_list,
147 - success=bool(alerts_by_rule_list),
148 - message="Successfully collected alerts by rule",
149 - )
150 -
151 -
152 -def get_alerts_by_rule_per_host(search_body: AlertsSearchBody) -> AlertsByRulePerHostResponse:
153 - aggregated_by_rule_per_host = collect_and_aggregate_alerts(["agent_name", "rule_description"], search_body)
154 - alerts_by_rule_per_host_list: List[AlertsByRulePerHost] = [
155 - AlertsByRulePerHost(agent_name=agent_name, rule=rule, number_of_alerts=count)
156 - for (agent_name, rule), count in aggregated_by_rule_per_host.items()
157 - ]
158 -
159 - return AlertsByRulePerHostResponse(
160 - alerts_by_rule_per_host=alerts_by_rule_per_host_list,
161 - success=bool(alerts_by_rule_per_host_list),
162 - message="Successfully collected alerts by rule per host",
163 - )
backend/app/connectors/wazuh_indexer/services/monitoring.py deleted
-126
@@ -1,126 +0,0 @@
1 -from typing import Dict
2 -from typing import Union
3 -
4 -from loguru import logger
5 -
6 -from app.connectors.wazuh_indexer.schema.monitoring import ClusterHealth
7 -from app.connectors.wazuh_indexer.schema.monitoring import ClusterHealthResponse
8 -from app.connectors.wazuh_indexer.schema.monitoring import IndicesStats
9 -from app.connectors.wazuh_indexer.schema.monitoring import IndicesStatsResponse
10 -from app.connectors.wazuh_indexer.schema.monitoring import NodeAllocation
11 -from app.connectors.wazuh_indexer.schema.monitoring import NodeAllocationResponse
12 -from app.connectors.wazuh_indexer.schema.monitoring import Shards
13 -from app.connectors.wazuh_indexer.schema.monitoring import ShardsResponse
14 -from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
15 -from app.connectors.wazuh_indexer.utils.universal import format_indices_stats
16 -from app.connectors.wazuh_indexer.utils.universal import format_node_allocation
17 -from app.connectors.wazuh_indexer.utils.universal import format_shards
18 -
19 -
20 -def cluster_healthcheck() -> Union[ClusterHealthResponse, Dict[str, str]]:
21 - """
22 - Returns the cluster health of the Wazuh Indexer service.
23 -
24 - Returns:
25 - ElasticsearchResponse: A Pydantic model containing the cluster health of the Wazuh Indexer service.
26 -
27 - Raises:
28 - Exception: An exception is raised if the cluster health cannot be retrieved.
29 - """
30 - logger.info("Collecting Wazuh Indexer healthcheck")
31 - es_client = create_wazuh_indexer_client("Wazuh-Indexer")
32 - try:
33 - cluster_health_data = es_client.cluster.health()
34 - cluster_health_model = ClusterHealth(**cluster_health_data)
35 - return ClusterHealthResponse(
36 - cluster_health=cluster_health_model,
37 - success=True,
38 - message="Successfully collected Wazuh Indexer cluster health",
39 - )
40 - except Exception as e:
41 - logger.error(f"Cluster health check failed with error: {e}")
42 - return {"success": False, "message": f"Cluster health check failed with error: {e}"}
43 -
44 -
45 -def node_allocation() -> Union[NodeAllocationResponse, Dict[str, bool]]:
46 - """
47 - Returns the node allocation of the Wazuh Indexer service.
48 -
49 - Returns:
50 - ElasticsearchResponse: A Pydantic model containing the node allocation of the Wazuh Indexer service.
51 -
52 - Raises:
53 - Exception: An exception is raised if the node allocation cannot be retrieved.
54 - """
55 - logger.info("Collecting Wazuh Indexer node allocation")
56 - es_client = create_wazuh_indexer_client("Wazuh-Indexer")
57 - try:
58 - raw_node_allocation_data = es_client.cat.allocation(format="json")
59 - logger.info(raw_node_allocation_data)
60 -
61 - formatted_node_allocation_data = format_node_allocation(raw_node_allocation_data)
62 -
63 - node_allocation_models = [NodeAllocation(**node) for node in formatted_node_allocation_data]
64 -
65 - return NodeAllocationResponse(
66 - node_allocation=node_allocation_models,
67 - success=True,
68 - message="Successfully collected Wazuh Indexer node allocation",
69 - )
70 - except Exception as e:
71 - logger.error(f"Node allocation check failed with error: {e}")
72 - return {"success": False, "message": f"Node allocation check failed with error: {e}"}
73 -
74 -
75 -def indices_stats() -> Union[IndicesStatsResponse, Dict[str, str]]:
76 - """
77 - Returns the indices stats of the Wazuh Indexer service.
78 -
79 - Returns:
80 - ElasticsearchResponse: A Pydantic model containing the indices stats of the Wazuh Indexer service.
81 -
82 - Raises:
83 - Exception: An exception is raised if the indices stats cannot be retrieved.
84 - """
85 - logger.info("Collecting Wazuh Indexer indices stats")
86 - es_client = create_wazuh_indexer_client("Wazuh-Indexer")
87 - try:
88 - raw_indices_stats_data = es_client.cat.indices(format="json")
89 -
90 - formatted_indices_stats_data = format_indices_stats(raw_indices_stats_data)
91 -
92 - indices_stats_models = [IndicesStats(**index) for index in formatted_indices_stats_data]
93 -
94 - return IndicesStatsResponse(
95 - indices_stats=indices_stats_models,
96 - success=True,
97 - message="Successfully collected Wazuh Indexer indices stats",
98 - )
99 - except Exception as e:
100 - logger.error(f"Indices stats check failed with error: {e}")
101 - return {"success": False, "message": f"Indices stats check failed with error: {e}"}
102 -
103 -
104 -def shards() -> Union[ShardsResponse, Dict[str, str]]:
105 - """
106 - Returns the shards of the Wazuh Indexer service.
107 -
108 - Returns:
109 - ElasticsearchResponse: A Pydantic model containing the shards of the Wazuh Indexer service.
110 -
111 - Raises:
112 - Exception: An exception is raised if the shards cannot be retrieved.
113 - """
114 - logger.info("Collecting Wazuh Indexer shards")
115 - es_client = create_wazuh_indexer_client("Wazuh-Indexer")
116 - try:
117 - raw_shards_data = es_client.cat.shards(format="json")
118 -
119 - formatted_shards_data = format_shards(raw_shards_data)
120 -
121 - shard_models = [Shards(**shard) for shard in formatted_shards_data]
122 -
123 - return ShardsResponse(shards=shard_models, success=True, message="Successfully collected Wazuh Indexer shards")
124 - except Exception as e:
125 - logger.error(f"Shards check failed with error: {e}")
126 - return {"success": False, "message": f"Shards check failed with error: {e}"}
backend/app/connectors/wazuh_indexer/utils/universal.py deleted
-288
@@ -1,288 +0,0 @@
1 -from datetime import datetime
2 -from datetime import timedelta
3 -from typing import Any
4 -from typing import Dict
5 -from typing import Iterable
6 -from typing import Tuple
7 -
8 -from elasticsearch7 import Elasticsearch
9 -from loguru import logger
10 -
11 -from app.connectors.utils import get_connector_info_from_db
12 -from app.connectors.wazuh_indexer.schema.indices import IndexConfigModel
13 -from app.connectors.wazuh_indexer.schema.indices import Indices
14 -
15 -
16 -def verify_wazuh_indexer_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
17 - """
18 - Verifies the connection to Wazuh Indexer service.
19 -
20 - Returns:
21 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
22 - """
23 - logger.info(f"Verifying the wazuh-indexer connection to {attributes['connector_url']}")
24 -
25 - try:
26 - es = Elasticsearch(
27 - [attributes["connector_url"]],
28 - http_auth=(attributes["connector_username"], attributes["connector_password"]),
29 - verify_certs=False,
30 - timeout=15,
31 - max_retries=10,
32 - retry_on_timeout=False,
33 - )
34 - es.cluster.health()
35 - logger.debug("Wazuh Indexer connection successful")
36 - return {"connectionSuccessful": True, "message": "Wazuh Indexer connection successful"}
37 - except Exception as e:
38 - logger.error(f"Connection to {attributes['connector_url']} failed with error: {e}")
39 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error: {e}"}
40 -
41 -
42 -def verify_wazuh_indexer_connection(connector_name: str) -> str:
43 - """
44 - Returns the authentication token for the Wazuh Indexer service.
45 -
46 - Returns:
47 - str: Authentication token for the Wazuh Indexer service.
48 - """
49 - attributes = get_connector_info_from_db(connector_name)
50 - if attributes is None:
51 - logger.error("No Wazuh Indexer connector found in the database")
52 - return None
53 - return verify_wazuh_indexer_credentials(attributes)
54 -
55 -
56 -def create_wazuh_indexer_client(connector_name: str) -> Elasticsearch:
57 - """
58 - Returns an Elasticsearch client for the Wazuh Indexer service.
59 -
60 - Returns:
61 - Elasticsearch: Elasticsearch client for the Wazuh Indexer service.
62 - """
63 - attributes = get_connector_info_from_db(connector_name)
64 - if attributes is None:
65 - logger.error("No Wazuh Indexer connector found in the database")
66 - return None
67 - return Elasticsearch(
68 - [attributes["connector_url"]],
69 - http_auth=(attributes["connector_username"], attributes["connector_password"]),
70 - verify_certs=False,
71 - timeout=15,
72 - max_retries=10,
73 - retry_on_timeout=False,
74 - )
75 -
76 -
77 -def format_node_allocation(node_allocation):
78 - """
79 - Format the node allocation details into a list of dictionaries. Each dictionary contains disk used, disk available, total disk, disk
80 - usage percentage, and node name.
81 -
82 - Args:
83 - node_allocation: Node allocation details from Elasticsearch.
84 -
85 - Returns:
86 - list: A list of dictionaries containing formatted node allocation details.
87 - """
88 - return [
89 - {
90 - "disk_used": node["disk.used"],
91 - "disk_available": node["disk.avail"],
92 - "disk_total": node["disk.total"],
93 - "disk_percent": node["disk.percent"],
94 - "node": node["node"],
95 - }
96 - for node in node_allocation
97 - ]
98 -
99 -
100 -def format_indices_stats(indices_stats):
101 - """
102 - Format the indices stats details into a list of dictionaries. Each dictionary contains the index name, the number of documents in the index,
103 - the size of the index, and the number of shards in the index.
104 -
105 - Args:
106 - indices_stats: Indices stats details from Elasticsearch.
107 -
108 - Returns:
109 - list: A list of dictionaries containing formatted indices stats details.
110 - """
111 - return [
112 - {
113 - "index": index["index"],
114 - "docs_count": index["docs.count"],
115 - "store_size": index["store.size"],
116 - "replica_count": index["rep"],
117 - "health": index["health"],
118 - }
119 - for index in indices_stats
120 - ]
121 -
122 -
123 -def format_shards(shards):
124 - """
125 - Format the shards details into a list of dictionaries. Each dictionary contains the index name, the shard number, the shard state, the shard
126 - size, and the node name.
127 -
128 - Args:
129 - shards: Shards details from Elasticsearch.
130 -
131 - Returns:
132 - list: A list of dictionaries containing formatted shards details.
133 - """
134 - return [
135 - {
136 - "index": shard["index"],
137 - "shard": shard["shard"],
138 - "state": shard["state"],
139 - "size": shard["store"],
140 - "node": shard["node"],
141 - }
142 - for shard in shards
143 - ]
144 -
145 -
146 -def collect_indices() -> Indices:
147 - """
148 - Collects the indices from Elasticsearch.
149 -
150 - Returns:
151 - dict: A dictionary containing the indices, shards, and indices stats.
152 - """
153 - logger.info("Collecting indices from Elasticsearch")
154 - es = create_wazuh_indexer_client("Wazuh-Indexer")
155 - try:
156 - indices_dict = es.indices.get_alias("*")
157 - indices_list = list(indices_dict.keys())
158 - # Check if the index is valid
159 - index_config = IndexConfigModel()
160 - indices_list = [index for index in indices_list if index_config.is_valid_index(index)]
161 - return Indices(indices_list=indices_list, success=True, message="Indices collected successfully")
162 - except Exception as e:
163 - logger.error(f"Failed to collect indices: {e}")
164 - return Indices(message="Failed to collect indices", success=False)
165 -
166 -
167 -class AlertsQueryBuilder:
168 - @staticmethod
169 - def _get_time_range_start(timerange: str) -> str:
170 - """
171 - Determines the start time of the time range based on the current time and the provided timerange.
172 -
173 - Args:
174 - timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
175 -
176 - Returns:
177 - str: A string representing the start time of the time range in ISO format.
178 - """
179 - if timerange.endswith("h"):
180 - delta = timedelta(hours=int(timerange[:-1]))
181 - elif timerange.endswith("d"):
182 - delta = timedelta(days=int(timerange[:-1]))
183 - elif timerange.endswith("w"):
184 - delta = timedelta(weeks=int(timerange[:-1]))
185 - else:
186 - raise ValueError("Invalid timerange format. Expected a string like '24h', '1d', '1w', etc.")
187 -
188 - start = datetime.utcnow() - delta
189 - return start.isoformat() + "Z" # Elasticsearch expects the time in ISO format with a Z at the end
190 -
191 - def __init__(self):
192 - self.query = {
193 - "query": {
194 - "bool": {
195 - "must": [],
196 - },
197 - },
198 - "sort": [],
199 - }
200 -
201 - def add_time_range(self, timerange: str, timestamp_field: str):
202 - start = self._get_time_range_start(timerange)
203 - self.query["query"]["bool"]["must"].append({"range": {timestamp_field: {"gte": start, "lte": "now"}}})
204 - return self
205 -
206 - def add_matches(self, matches: Iterable[Tuple[str, str]]):
207 - for field, value in matches:
208 - self.query["query"]["bool"]["must"].append({"match": {field: value}})
209 - return self
210 -
211 - def add_match_phrase(self, matches: Iterable[Tuple[str, str]]):
212 - for field, value in matches:
213 - self.query["query"]["bool"]["must"].append({"match_phrase": {field: value}})
214 - return self
215 -
216 - def add_range(self, field: str, value: str):
217 - self.query["query"]["bool"]["must"].append({"range": {field: {"gte": value}}})
218 - return self
219 -
220 - def add_sort(self, field: str, order: str = "desc"):
221 - self.query["sort"].append({field: {"order": order}})
222 - return self
223 -
224 - def build(self):
225 - return self.query
226 -
227 -
228 -class LogsQueryBuilder:
229 - @staticmethod
230 - def _get_time_range_start(timerange: str) -> str:
231 - """
232 - Determines the start time of the time range based on the current time and the provided timerange.
233 -
234 - Args:
235 - timerange (str): The time range to collect alerts from. This is a string like "24h", "1w", etc.
236 -
237 - Returns:
238 - str: A string representing the start time of the time range in ISO format.
239 - """
240 - if timerange.endswith("m"):
241 - delta = timedelta(minutes=int(timerange[:-1]))
242 - elif timerange.endswith("h"):
243 - delta = timedelta(hours=int(timerange[:-1]))
244 - elif timerange.endswith("d"):
245 - delta = timedelta(days=int(timerange[:-1]))
246 - elif timerange.endswith("w"):
247 - delta = timedelta(weeks=int(timerange[:-1]))
248 - else:
249 - raise ValueError("Invalid timerange format. Expected a string like '24h', '1d', '1w', '1m', etc.")
250 -
251 - start = datetime.utcnow() - delta
252 - return start.isoformat() + "Z" # Elasticsearch expects the time in ISO format with a Z at the end
253 -
254 - def __init__(self):
255 - self.query = {
256 - "query": {
257 - "bool": {
258 - "must": [],
259 - },
260 - },
261 - "sort": [],
262 - }
263 -
264 - def add_time_range(self, timerange: str, timestamp_field: str):
265 - start = self._get_time_range_start(timerange)
266 - self.query["query"]["bool"]["must"].append({"range": {timestamp_field: {"gte": start, "lte": "now"}}})
267 - return self
268 -
269 - def add_matches(self, matches: Iterable[Tuple[str, str]]):
270 - for field, value in matches:
271 - self.query["query"]["bool"]["must"].append({"match": {field: value}})
272 - return self
273 -
274 - def add_match_phrase(self, matches: Iterable[Tuple[str, str]]):
275 - for field, value in matches:
276 - self.query["query"]["bool"]["must"].append({"match_phrase": {field: value}})
277 - return self
278 -
279 - def add_range(self, field: str, value: str):
280 - self.query["query"]["bool"]["must"].append({"range": {field: {"gte": value}}})
281 - return self
282 -
283 - def add_sort(self, field: str, order: str = "desc"):
284 - self.query["sort"].append({field: {"order": order}})
285 - return self
286 -
287 - def build(self):
288 - return self.query
backend/app/connectors/wazuh_manager/models/rules.py deleted
-16
@@ -1,16 +0,0 @@
1 -import datetime
2 -from typing import Optional
3 -
4 -from sqlmodel import Field
5 -from sqlmodel import SQLModel
6 -
7 -
8 -class DisabledRule(SQLModel, table=True):
9 - id: Optional[int] = Field(primary_key=True)
10 - rule_id: str = Field(index=True)
11 - previous_level: str = Field(max_length=256)
12 - new_level: str = Field(max_length=256)
13 - reason_for_disabling: str = Field(max_length=256)
14 - length_of_time: str = Field(max_length=256)
15 - date_disabled: datetime.datetime = datetime.datetime.now()
16 - disabled_by: str = Field(max_length=256)
backend/app/connectors/wazuh_manager/routes/rules.py deleted
-100
@@ -1,100 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import Depends
3 -from fastapi import HTTPException
4 -from fastapi import Security
5 -
6 -# App specific imports
7 -from app.auth.routes.auth import AuthHandler
8 -from app.connectors.wazuh_manager.models.rules import DisabledRule
9 -from app.connectors.wazuh_manager.schema.rules import AllDisabledRuleResponse
10 -from app.connectors.wazuh_manager.schema.rules import RuleDisable
11 -from app.connectors.wazuh_manager.schema.rules import RuleDisableResponse
12 -from app.connectors.wazuh_manager.schema.rules import RuleEnable
13 -from app.connectors.wazuh_manager.schema.rules import RuleEnableResponse
14 -from app.connectors.wazuh_manager.schema.rules import RuleExclude
15 -from app.connectors.wazuh_manager.schema.rules import RuleExcludeResponse
16 -from app.connectors.wazuh_manager.services.rules import disable_rule
17 -from app.connectors.wazuh_manager.services.rules import enable_rule
18 -from app.connectors.wazuh_manager.services.rules import exclude_rule
19 -from app.db.db_session import session
20 -
21 -NEW_LEVEL = "1"
22 -wazuh_manager_router = APIRouter()
23 -auth_handler = AuthHandler()
24 -
25 -
26 -def query_disabled_rule(rule_id: str):
27 - return session.query(DisabledRule).filter(DisabledRule.rule_id == rule_id).first()
28 -
29 -
30 -@wazuh_manager_router.get(
31 - "/rule/disabled",
32 - response_model=AllDisabledRuleResponse,
33 - description="Get all disabled rules",
34 - dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
35 -)
36 -async def get_disabled_rules() -> AllDisabledRuleResponse:
37 - disabled_rules = session.query(DisabledRule).all()
38 - return AllDisabledRuleResponse(disabled_rules=disabled_rules, success=True, message="Successfully fetched all disabled rules")
39 -
40 -
41 -@wazuh_manager_router.post(
42 - "/rule/disable",
43 - response_model=RuleDisableResponse,
44 - description="Disable a Wazuh Rule",
45 - dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
46 -)
47 -async def disable_wazuh_rule(rule: RuleDisable, username: str = Depends(auth_handler.get_current_user)) -> RuleDisableResponse:
48 - if query_disabled_rule(rule.rule_id):
49 - raise HTTPException(status_code=404, detail="Rule is already disabled")
50 -
51 - rule_disabled = disable_rule(rule)
52 - if rule_disabled:
53 - new_disabled_rule = DisabledRule(
54 - rule_id=rule.rule_id,
55 - previous_level=rule_disabled.previous_level,
56 - new_level=NEW_LEVEL,
57 - reason_for_disabling=rule.reason_for_disabling,
58 - length_of_time=rule.length_of_time,
59 - disabled_by=username.username,
60 - )
61 - session.add(new_disabled_rule)
62 - session.commit()
63 - return rule_disabled
64 - else:
65 - raise HTTPException(status_code=404, detail="Was not able to disable rule")
66 -
67 -
68 -@wazuh_manager_router.post(
69 - "/rule/enable",
70 - response_model=RuleEnableResponse,
71 - description="Enable a Wazuh Rule",
72 - dependencies=[Security(AuthHandler().get_current_user, scopes=["admin"])],
73 -)
74 -async def enable_wazuh_rule(rule: RuleEnable) -> RuleEnableResponse:
75 - disabled_rule = query_disabled_rule(rule.rule_id)
76 - if not disabled_rule:
77 - raise HTTPException(status_code=404, detail="Rule is already enabled")
78 -
79 - previous_level = disabled_rule.previous_level
80 - rule_enabled = enable_rule(rule, previous_level)
81 -
82 - if rule_enabled:
83 - session.delete(disabled_rule)
84 - session.commit()
85 - return rule_enabled
86 - else:
87 - raise HTTPException(status_code=404, detail="Was not able to enable rule")
88 -
89 -
90 -@wazuh_manager_router.post(
91 - "/rule/exclude",
92 - response_model=RuleExcludeResponse,
93 - description="Retrieve recommended exclusion for a Wazuh Rule",
94 -)
95 -async def exclude_wazuh_rule(rule: RuleExclude) -> RuleExcludeResponse:
96 - recommended_exclusion = exclude_rule(rule)
97 - if recommended_exclusion:
98 - return recommended_exclusion
99 - else:
100 - raise HTTPException(status_code=404, detail="Was not able to exclude rule")
backend/app/connectors/wazuh_manager/schema/rules.py deleted
-66
@@ -1,66 +0,0 @@
1 -from typing import List
2 -from typing import Optional
3 -
4 -from pydantic import BaseModel
5 -from pydantic import Field
6 -
7 -
8 -class RuleDisable(BaseModel):
9 - rule_id: str
10 - reason_for_disabling: str
11 - length_of_time: str
12 -
13 -
14 -class RuleDisableResponse(BaseModel):
15 - previous_level: Optional[str]
16 - message: str
17 - success: bool
18 -
19 -
20 -class RuleEnable(BaseModel):
21 - rule_id: str
22 - reason_for_enabling: str
23 -
24 -
25 -class RuleEnableResponse(BaseModel):
26 - new_level: Optional[str]
27 - message: str
28 - success: bool
29 -
30 -
31 -class AllDisabledRule(BaseModel):
32 - rule_id: str
33 - previous_level: str
34 - new_level: str
35 - reason_for_disabling: str
36 - length_of_time: str
37 - disabled_by: str
38 -
39 -
40 -class AllDisabledRuleResponse(BaseModel):
41 - disabled_rules: List[AllDisabledRule]
42 - success: bool
43 - message: str
44 -
45 -
46 -class RuleExclude(BaseModel):
47 - rule_value: str = Field(
48 - ...,
49 - description="The value of the field trying to be exclude",
50 - example="C:\\Windows\\ServiceState\\EventLog\\Data\\lastalive1.dat",
51 - )
52 - input_value: str = Field(
53 - ...,
54 - description="The proposed value of the field trying to be exclude that would result in an exclusiong",
55 - example="C:\\\\Windows\\\\ServiceState\\\\EventLog\\\\Data\\\\lastalive1\.dat",
56 - )
57 -
58 -
59 -class RuleExcludeResponse(BaseModel):
60 - success: bool
61 - message: str
62 - recommended_exclusion: str = Field(
63 - ...,
64 - description="The recommended exclusion for the rule",
65 - example="C:\\\\Windows\\\\ServiceState\\\\EventLog\\\\Data\\\\lastalive1\.dat",
66 - )
backend/app/connectors/wazuh_manager/services/rules.py deleted
-142
@@ -1,142 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import List
4 -from typing import Tuple
5 -from typing import Union
6 -
7 -import pcre2
8 -import xmltodict
9 -from loguru import logger
10 -
11 -from app.connectors.wazuh_manager.schema.rules import RuleDisable
12 -from app.connectors.wazuh_manager.schema.rules import RuleDisableResponse
13 -from app.connectors.wazuh_manager.schema.rules import RuleEnable
14 -from app.connectors.wazuh_manager.schema.rules import RuleEnableResponse
15 -from app.connectors.wazuh_manager.schema.rules import RuleExclude
16 -from app.connectors.wazuh_manager.schema.rules import RuleExcludeResponse
17 -from app.connectors.wazuh_manager.utils.universal import restart_service
18 -from app.connectors.wazuh_manager.utils.universal import send_get_request
19 -from app.connectors.wazuh_manager.utils.universal import send_put_request
20 -
21 -
22 -def fetch_filename(rule_id: str) -> str:
23 - endpoint = "rules"
24 - params = {"rule_ids": rule_id}
25 - filename_data = send_get_request(endpoint=endpoint, params=params)
26 - if not filename_data["success"]:
27 - raise ValueError(filename_data["message"])
28 - return filename_data["data"]["data"]["affected_items"][0]["filename"]
29 -
30 -
31 -def fetch_file_content(filename: str) -> str:
32 - endpoint = f"rules/files/{filename}"
33 - file_content_data = send_get_request(endpoint=endpoint)
34 - if not file_content_data["success"]:
35 - raise ValueError(file_content_data["message"])
36 - return file_content_data["data"]["data"]["affected_items"][0]["group"]
37 -
38 -
39 -def set_rule_level(file_content: Any, rule_id: str, new_level: str) -> Tuple[str, Any]:
40 - previous_level = None
41 - if isinstance(file_content, dict):
42 - file_content = [file_content]
43 - for group_block in file_content:
44 - rule_block = group_block.get("rule", None)
45 - if rule_block:
46 - if isinstance(rule_block, dict):
47 - rule_block = [rule_block]
48 - for rule in rule_block:
49 - if rule["@id"] == rule_id:
50 - previous_level = rule["@level"]
51 - rule["@level"] = new_level
52 - break
53 - return previous_level, file_content
54 -
55 -
56 -def convert_to_xml(updated_file_content: Union[Dict[str, str], List[Dict[str, str]]]) -> str:
57 - xml_content_list = []
58 - for group in updated_file_content:
59 - xml_dict = {"group": group}
60 - xml_content = xmltodict.unparse(xml_dict, pretty=True)
61 - xml_content = xml_content.replace('<?xml version="1.0" encoding="utf-8"?>', "")
62 - xml_content_list.append(xml_content)
63 - xml_content = "\n".join(xml_content_list)
64 - xml_content = xml_content.strip()
65 - return xml_content
66 -
67 -
68 -def upload_updated_rule(filename: str, xml_content: str):
69 - response = send_put_request(
70 - endpoint=f"rules/files/{filename}",
71 - data=xml_content,
72 - params={"overwrite": "true"},
73 - )
74 - if not response["success"]:
75 - raise ValueError(response["message"])
76 -
77 -
78 -def process_rule(rule, rule_action_func, ResponseModel):
79 - filename, file_content = fetch_filename_and_content(rule.rule_id)
80 - previous_level, updated_file_content = rule_action_func(file_content, rule.rule_id)
81 - xml_content = convert_to_xml(updated_file_content)
82 - upload_updated_rule(filename, xml_content)
83 - restart_service()
84 - return ResponseModel(
85 - previous_level=previous_level,
86 - success=True,
87 - message=f"Rule {rule.rule_id} successfully processed in file {filename}.",
88 - )
89 -
90 -
91 -def fetch_filename_and_content(rule_id: str) -> Tuple[str, str]:
92 - filename = fetch_filename(rule_id)
93 - file_content = fetch_file_content(filename)
94 - return filename, file_content
95 -
96 -
97 -def disable_rule(rule: RuleDisable) -> RuleDisableResponse:
98 - return process_rule(rule, lambda fc, rid: set_rule_level(fc, rid, "1"), RuleDisableResponse)
99 -
100 -
101 -def enable_rule(rule: RuleEnable, previous_level: str) -> RuleEnableResponse:
102 - return process_rule(rule, lambda fc, rid: set_rule_level(fc, rid, previous_level), RuleEnableResponse)
103 -
104 -
105 -################# ! EXCLUDE RULE ! #################
106 -
107 -
108 -def make_pcre2_compatible(input_string: str) -> str:
109 - """
110 - Convert the input string to a PCRE2 compatible regex pattern.
111 -
112 - Parameters:
113 - - input_string (str): The input string to convert.
114 -
115 - Returns:
116 - - str: The PCRE2 compatible regex pattern.
117 - """
118 - # PCRE2 uses \\ to escape a backslash
119 - return input_string.replace("\\", "\\\\")
120 -
121 -
122 -def exclude_rule(rule: RuleExclude) -> RuleExcludeResponse:
123 - try:
124 - # Convert rule_value to a PCRE2 compatible regex pattern
125 - pcre2_pattern = make_pcre2_compatible(rule.rule_value)
126 -
127 - compiled_pattern = pcre2.compile(pcre2_pattern)
128 - print(f"Compiled Pattern: {compiled_pattern}") # Debugging line
129 -
130 - print(f"Input Value: {rule.input_value}") # Debugging line
131 -
132 - match_data = compiled_pattern.match(rule.input_value)
133 -
134 - if match_data:
135 - return RuleExcludeResponse(success=True, message="Successfully excluded rule", recommended_exclusion=rule.input_value)
136 - else:
137 - return RuleExcludeResponse(success=False, message="Failed to exclude rule", recommended_exclusion="")
138 -
139 - except Exception as e:
140 - print(f"Exception: {e}") # Debugging line
141 - logger.error(f"Failed to exclude rule: {e}")
142 - return RuleExcludeResponse(success=False, message=f"Failed to exclude rule: {e}", recommended_exclusion="")
backend/app/connectors/wazuh_manager/utils/universal.py deleted
-253
@@ -1,253 +0,0 @@
1 -from typing import Any
2 -from typing import Dict
3 -from typing import Optional
4 -
5 -import requests
6 -from loguru import logger
7 -
8 -from app.connectors.utils import get_connector_info_from_db
9 -
10 -
11 -def verify_wazuh_manager_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
12 - """
13 - Verifies the connection to Wazuh manager service.
14 -
15 - Returns:
16 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
17 - """
18 - logger.info(f"Verifying the wazuh-manager connection to {attributes['connector_url']}")
19 -
20 - try:
21 - wazuh_auth_token = requests.get(
22 - f"{attributes['connector_url']}/security/user/authenticate",
23 - auth=(
24 - attributes["connector_username"],
25 - attributes["connector_password"],
26 - ),
27 - verify=False,
28 - )
29 -
30 - if wazuh_auth_token.status_code == 200:
31 - logger.debug("Wazuh Authentication Token successful")
32 - return {"connectionSuccessful": True, "message": "Wazuh Manager authentication successful"}
33 - else:
34 - logger.error(f"Connection to {attributes['connector_url']} failed with error: {wazuh_auth_token.text}")
35 -
36 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed"}
37 - except Exception as e:
38 - logger.error(f"Connection to {attributes['connector_url']} failed with error: {e}")
39 -
40 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error."}
41 -
42 -
43 -def verify_wazuh_manager_connection(connector_name: str) -> str:
44 - """
45 - Returns the authentication token for the Wazuh manager service.
46 -
47 - Returns:
48 - str: Authentication token for the Wazuh manager service.
49 - """
50 - logger.info("Getting Wazuh Manager authentication token")
51 - attributes = get_connector_info_from_db(connector_name)
52 - if attributes is None:
53 - logger.error("No Wazuh Manager connector found in the database")
54 - return None
55 - return verify_wazuh_manager_credentials(attributes)
56 -
57 -
58 -def create_wazuh_manager_client(connector_name: str) -> str:
59 - """
60 - Returns the authentication token for the Wazuh manager service.
61 -
62 - Returns:
63 - str: Authentication token for the Wazuh manager service.
64 - """
65 - logger.info("Getting Wazuh Manager authentication token")
66 - attributes = get_connector_info_from_db(connector_name)
67 - if attributes is None:
68 - logger.error("No Wazuh Manager connector found in the database")
69 - return None
70 - try:
71 - wazuh_auth_token = requests.get(
72 - f"{attributes['connector_url']}/security/user/authenticate",
73 - auth=(
74 - attributes["connector_username"],
75 - attributes["connector_password"],
76 - ),
77 - verify=False,
78 - )
79 -
80 - if wazuh_auth_token.status_code == 200:
81 - logger.debug("Wazuh Authentication Token successful")
82 - wazuh_auth_token = wazuh_auth_token.json()
83 - wazuh_auth_token = wazuh_auth_token["data"]["token"]
84 -
85 - return {"Authorization": f"Bearer {wazuh_auth_token}"}
86 - else:
87 - logger.error(f"Connection to {attributes['connector_url']} failed with error: {wazuh_auth_token.text}")
88 -
89 - return None
90 - except Exception as e:
91 - logger.error(f"Connection to {attributes['connector_url']} failed with error: {e}")
92 -
93 - return None
94 -
95 -
96 -def send_get_request(endpoint: str, params: Optional[Dict[str, Any]] = None, connector_name: str = "Wazuh-Manager") -> Dict[str, Any]:
97 - """
98 - Sends a GET request to the Wazuh Manager service.
99 -
100 - Args:
101 - endpoint (str): The endpoint to send the GET request to.
102 - params (Optional[Dict[str, Any]], optional): The parameters to send with the GET request. Defaults to None.
103 - connector_name (str, optional): The name of the connector to use. Defaults to "Wazuh-Manager".
104 -
105 - Returns:
106 - Dict[str, Any]: The response from the GET request.
107 - """
108 - logger.info(f"Sending GET request to {endpoint}")
109 - wazuh_manager_client = create_wazuh_manager_client(connector_name)
110 - attributes = get_connector_info_from_db(connector_name)
111 - if attributes is None:
112 - logger.error("No Wazuh Manager connector found in the database")
113 - return None
114 - try:
115 - response = requests.get(
116 - f"{attributes['connector_url']}/{endpoint}",
117 - headers=wazuh_manager_client,
118 - params=params,
119 - verify=False,
120 - )
121 - response.raise_for_status()
122 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
123 - except Exception as e:
124 - logger.error(f"Failed to send GET request to {endpoint} with error: {e}")
125 - return {"success": False, "message": f"Failed to send GET request to {endpoint} with error: {e}"}
126 -
127 -
128 -def send_post_request(endpoint: str, data: Dict[str, Any], connector_name: str = "Wazuh-Manager") -> Dict[str, Any]:
129 - """
130 - Sends a POST request to the Wazuh Manager service.
131 -
132 - Args:
133 - endpoint (str): The endpoint to send the POST request to.
134 - data (Dict[str, Any]): The data to send with the POST request.
135 - connector_name (str, optional): The name of the connector to use. Defaults to "Wazuh-Manager".
136 -
137 - Returns:
138 - Dict[str, Any]: The response from the POST request.
139 - """
140 - logger.info(f"Sending POST request to {endpoint}")
141 - wazuh_manager_client = create_wazuh_manager_client(connector_name)
142 - attributes = get_connector_info_from_db(connector_name)
143 - if attributes is None:
144 - logger.error("No Wazuh Manager connector found in the database")
145 - return None
146 - try:
147 - response = requests.post(
148 - f"{attributes['connector_url']}/{endpoint}",
149 - headers=wazuh_manager_client,
150 - json=data,
151 - verify=False,
152 - )
153 - response.raise_for_status()
154 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
155 - except Exception as e:
156 - logger.error(f"Failed to send POST request to {endpoint} with error: {e}")
157 - return {"success": False, "message": f"Failed to send POST request to {endpoint} with error: {e}"}
158 -
159 -
160 -def send_put_request(
161 - endpoint: str,
162 - data: Optional[Dict[str, Any]],
163 - params: Optional[Dict[str, str]] = None,
164 - connector_name: str = "Wazuh-Manager",
165 -) -> Dict[str, Any]:
166 - """
167 - Sends a PUT request to the Wazuh Manager service.
168 -
169 - Args:
170 - endpoint (str): The endpoint to send the PUT request to.
171 - data (Dict[str, Any]): The data to send with the PUT request.
172 - connector_name (str, optional): The name of the connector to use. Defaults to "Wazuh-Manager".
173 -
174 - Returns:
175 - Dict[str, Any]: The response from the PUT request.
176 - """
177 - logger.info(f"Sending PUT request to {endpoint}")
178 - wazuh_manager_client = create_wazuh_manager_client(connector_name)
179 - attributes = get_connector_info_from_db(connector_name)
180 - if attributes is None:
181 - logger.error("No Wazuh Manager connector found in the database")
182 - return None
183 - try:
184 - response = requests.put(
185 - f"{attributes['connector_url']}/{endpoint}",
186 - headers=wazuh_manager_client,
187 - params=params,
188 - data=data,
189 - verify=False,
190 - )
191 - response.raise_for_status()
192 - return {"data": response.json(), "success": True, "message": "Successfully retrieved data"}
193 - except Exception as e:
194 - logger.error(f"Failed to send PUT request to {endpoint} with error: {e}")
195 - return {"success": False, "message": f"Failed to send PUT request to {endpoint} with error: {e}"}
196 -
197 -
198 -def send_delete_request(endpoint: str, params: Optional[Dict[str, Any]] = None, connector_name: str = "Wazuh-Manager") -> Dict[str, Any]:
199 - """
200 - Sends a DELETE request to the Wazuh Manager service.
201 -
202 - Args:
203 - endpoint (str): The endpoint to send the DELETE request to.
204 - params (Optional[Dict[str, Any]], optional): The parameters to send with the DELETE request. Defaults to None.
205 - connector_name (str, optional): The name of the connector to use. Defaults to "Wazuh-Manager".
206 -
207 - Returns:
208 - Dict[str, Any]: The response from the DELETE request.
209 - """
210 - logger.info(f"Sending DELETE request to {endpoint}")
211 - wazuh_manager_client = create_wazuh_manager_client(connector_name)
212 - attributes = get_connector_info_from_db(connector_name)
213 - if attributes is None:
214 - logger.error("No Wazuh Manager connector found in the database")
215 - return None
216 - try:
217 - response = requests.delete(
218 - f"{attributes['connector_url']}/{endpoint}",
219 - headers=wazuh_manager_client,
220 - params=params,
221 - verify=False,
222 - )
223 - response.raise_for_status()
224 - return {"data": response.json(), "success": True, "message": "Successfully deleted data"}
225 - except Exception as e:
226 - logger.error(f"Failed to send DELETE request to {endpoint} with error: {e}")
227 - return {"success": False, "message": f"Failed to send DELETE request to {endpoint} with error: {e}"}
228 -
229 -
230 -def restart_service(connector_name: str = "Wazuh-Manager") -> Dict[str, Any]:
231 - """
232 - Restarts the Wazuh Manager service.
233 -
234 - Returns:
235 - Dict[str, Any]: The response from the DELETE request.
236 - """
237 - logger.info("Restarting Wazuh Manager service")
238 - wazuh_manager_client = create_wazuh_manager_client(connector_name)
239 - attributes = get_connector_info_from_db(connector_name)
240 - if attributes is None:
241 - logger.error("No Wazuh Manager connector found in the database")
242 - return None
243 - try:
244 - response = requests.put(
245 - f"{attributes['connector_url']}/manager/restart",
246 - headers=wazuh_manager_client,
247 - verify=False,
248 - )
249 - response.raise_for_status()
250 - return {"data": response.json(), "success": True, "message": "Successfully restarted service"}
251 - except Exception as e:
252 - logger.error(f"Failed to restart Wazuh Manager service with error: {e}")
253 - return {"success": False, "message": f"Failed to restart Wazuh Manager service with error: {e}"}
backend/app/customers/routes/customers.py deleted
-239
@@ -1,239 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import HTTPException
3 -from fastapi import Query
4 -from loguru import logger
5 -from starlette.status import HTTP_401_UNAUTHORIZED
6 -
7 -# App specific imports
8 -from app.customers.schema.customers import AgentModel
9 -from app.customers.schema.customers import AgentsResponse
10 -from app.customers.schema.customers import CustomerFullResponse
11 -from app.customers.schema.customers import CustomerMetaRequestBody
12 -from app.customers.schema.customers import CustomerMetaResponse
13 -from app.customers.schema.customers import CustomerRequestBody
14 -from app.customers.schema.customers import CustomerResponse
15 -from app.customers.schema.customers import CustomersResponse
16 -from app.db.db_session import session
17 -from app.db.universal_models import Agents
18 -from app.db.universal_models import Customers
19 -from app.db.universal_models import CustomersMeta
20 -
21 -# from app.healthchecks.agents.schema.agents import AgentModel
22 -from app.healthchecks.agents.schema.agents import AgentHealthCheckResponse
23 -from app.healthchecks.agents.schema.agents import TimeCriteriaModel
24 -from app.healthchecks.agents.services.agents import velociraptor_agents_healthcheck
25 -from app.healthchecks.agents.services.agents import wazuh_agents_healthcheck
26 -
27 -customers_router = APIRouter()
28 -
29 -
30 -def verify_admin(user):
31 - if not user.is_admin:
32 - raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Unauthorized")
33 -
34 -
35 -def verify_unique_customer_code(customer: CustomerRequestBody):
36 - existing_customer = session.query(Customers).filter(Customers.customer_code == customer.customer_code).first()
37 - if existing_customer:
38 - raise HTTPException(status_code=400, detail="Customer with this customer_code already exists")
39 -
40 -
41 -@customers_router.post("", response_model=CustomerResponse, description="Create a new customer")
42 -async def create_customer(customer: CustomerRequestBody) -> CustomerResponse:
43 - verify_unique_customer_code(customer)
44 - logger.info(f"Creating new customer: {customer}")
45 - new_customer = Customers(**customer.dict())
46 - session.add(new_customer)
47 - session.commit()
48 - return CustomerResponse(customer=customer, success=True, message="Customer created successfully")
49 -
50 -
51 -@customers_router.get("", response_model=CustomersResponse, description="Get all customers")
52 -async def get_customers() -> CustomersResponse:
53 - logger.info("Fetching all customers")
54 - customers = session.query(Customers).all()
55 - # Explode the customers list into a list of Customer objects
56 - customers = [CustomerRequestBody.parse_obj(customer.__dict__) for customer in customers]
57 - return CustomersResponse(customers=customers, success=True, message="Customers fetched successfully")
58 -
59 -
60 -@customers_router.get("/{customer_code}", response_model=CustomerResponse, description="Get customer by customer_code")
61 -async def get_customer(customer_code: str) -> CustomerResponse:
62 - logger.info(f"Fetching customer with customer_code: {customer_code}")
63 - customer = session.query(Customers).filter(Customers.customer_code == customer_code).first()
64 - if not customer:
65 - raise HTTPException(status_code=404, detail=f"Customer with customer_code {customer_code} not found")
66 - return CustomerResponse(
67 - customer=CustomerRequestBody.parse_obj(customer.__dict__),
68 - success=True,
69 - message="Customer fetched successfully",
70 - )
71 -
72 -
73 -@customers_router.put("/{customer_code}", response_model=CustomerResponse, description="Update customer by customer_code")
74 -async def update_customer(customer_code: str, customer: CustomerRequestBody) -> CustomerResponse:
75 - logger.info(f"Updating customer with customer_code: {customer_code}")
76 - existing_customer = session.query(Customers).filter(Customers.customer_code == customer_code).first()
77 - if not existing_customer:
78 - raise HTTPException(status_code=404, detail=f"Customer with customer_code {customer_code} not found")
79 - existing_customer.update_from_model(customer)
80 - session.commit()
81 - return CustomerResponse(
82 - customer=CustomerRequestBody.parse_obj(customer.__dict__),
83 - success=True,
84 - message="Customer updated successfully",
85 - )
86 -
87 -
88 -# ! TODO: Fix delete customer
89 -# @customers_router.delete("/{customer_code}", response_model=CustomerResponse, description="Delete customer by customer_code")
90 -# async def delete_customer(customer_code: str) -> CustomerResponse:
91 -# logger.info(f"Deleting customer with customer_code: {customer_code}")
92 -# existing_customer = session.query(Customers).filter(Customers.customer_code == customer_code).first()
93 -# if not existing_customer:
94 -# raise HTTPException(status_code=404, detail=f"Customer with customer_code {customer_code} not found")
95 -# session.delete(existing_customer)
96 -# session.commit()
97 -# return CustomerResponse(customer=CustomerRequestBody.parse_obj(existing_customer.__dict__), success=True, message="Customer deleted successfully")
98 -
99 -
100 -@customers_router.post("/{customer_code}/meta", response_model=CustomerMetaResponse, description="Add new customer meta")
101 -async def add_customer_meta(customer_code: str, customer_meta: CustomerMetaRequestBody) -> CustomerMetaResponse:
102 - logger.info(f"Adding new customer meta: {customer_meta}")
103 - existing_customer = session.query(Customers).filter(Customers.customer_code == customer_code).first()
104 - if not existing_customer:
105 - raise HTTPException(status_code=404, detail=f"Customer with customer_code {customer_code} not found")
106 - # Get the customer_code and customer_name from the existing customer and add it to the customer_meta object
107 - logger.info(f"Got existing customer: {existing_customer}")
108 - new_customer_meta = CustomersMeta(**customer_meta.dict())
109 - new_customer_meta.customer_code = existing_customer.customer_code
110 - new_customer_meta.customer_name = existing_customer.customer_name
111 - session.add(new_customer_meta)
112 - session.commit()
113 - return CustomerMetaResponse(customer_meta=customer_meta, success=True, message="Customer meta added successfully")
114 -
115 -
116 -@customers_router.get("/{customer_code}/meta", response_model=CustomerMetaResponse, description="Get customer meta by customer_code")
117 -async def get_customer_meta(customer_code: str) -> CustomerMetaResponse:
118 - logger.info(f"Fetching customer meta with customer_code: {customer_code}")
119 - customer_meta = session.query(CustomersMeta).filter(CustomersMeta.customer_code == customer_code).first()
120 - if not customer_meta:
121 - raise HTTPException(status_code=404, detail=f"Customer meta with customer_code {customer_code} not found")
122 - return CustomerMetaResponse(
123 - customer_meta=CustomerMetaRequestBody.parse_obj(customer_meta.__dict__),
124 - success=True,
125 - message="Customer meta fetched successfully",
126 - )
127 -
128 -
129 -@customers_router.put("/{customer_code}/meta", response_model=CustomerMetaResponse, description="Update customer meta by customer_code")
130 -async def update_customer_meta(customer_code: str, customer_meta: CustomerMetaRequestBody) -> CustomerMetaResponse:
131 - logger.info(f"Updating customer meta with customer_code: {customer_code}")
132 - existing_customer_meta = session.query(CustomersMeta).filter(CustomersMeta.customer_code == customer_code).first()
133 - if not existing_customer_meta:
134 - raise HTTPException(status_code=404, detail=f"Customer meta with customer_code {customer_code} not found")
135 -
136 - # Update the existing record with new values
137 - existing_customer_meta.update_from_model(customer_meta)
138 - logger.info(f"Updated existing customer meta: {existing_customer_meta}")
139 -
140 - # Commit the changes to the database
141 - session.commit()
142 -
143 - return CustomerMetaResponse(
144 - customer_meta=CustomerMetaRequestBody.parse_obj(customer_meta.__dict__),
145 - success=True,
146 - message="Customer meta updated successfully",
147 - )
148 -
149 -
150 -# ! TODO: Fix delete customer meta
151 -# @customers_router.delete("/{customer_code}/meta", response_model=CustomerMetaResponse, description="Delete customer meta by customer_code")
152 -# async def delete_customer_meta(customer_code: str) -> CustomerMetaResponse:
153 -# logger.info(f"Deleting customer meta with customer_code: {customer_code}")
154 -# existing_customer_meta = session.query(CustomersMeta).filter(CustomersMeta.customer_code == customer_code).first()
155 -# if not existing_customer_meta:
156 -# raise HTTPException(status_code=404, detail=f"Customer meta with customer_code {customer_code} not found")
157 -# session.delete(existing_customer_meta)
158 -# session.commit()
159 -# return CustomerMetaResponse(customer_meta=CustomerMetaRequestBody.parse_obj(existing_customer_meta.__dict__), success=True, message="Customer meta deleted successfully")
160 -
161 -
162 -@customers_router.get(
163 - "/{customer_code}/full",
164 - response_model=CustomerFullResponse,
165 - description="Get customer and customer meta by customer_code",
166 -)
167 -async def get_customer_full(customer_code: str) -> CustomerFullResponse:
168 - logger.info(f"Fetching customer and customer meta with customer_code: {customer_code}")
169 - customer = session.query(Customers).filter(Customers.customer_code == customer_code).first()
170 - if not customer:
171 - raise HTTPException(status_code=404, detail=f"Customer with customer_code {customer_code} not found")
172 - customer_meta = session.query(CustomersMeta).filter(CustomersMeta.customer_code == customer_code).first()
173 - if not customer_meta:
174 - raise HTTPException(status_code=404, detail=f"Customer meta with customer_code {customer_code} not found")
175 - return CustomerFullResponse(
176 - customer=CustomerRequestBody.parse_obj(customer.__dict__),
177 - customer_meta=CustomerMetaRequestBody.parse_obj(customer_meta.__dict__),
178 - success=True,
179 - message="Customer and customer meta fetched successfully",
180 - )
181 -
182 -
183 -# Get Agents for the given customer_code
184 -@customers_router.get("/{customer_code}/agents", response_model=AgentsResponse, description="Get agents for the given customer_code")
185 -async def get_agents(customer_code: str) -> AgentsResponse:
186 - logger.info(f"Fetching agents for customer_code: {customer_code}")
187 - customer = session.query(Customers).filter(Customers.customer_code == customer_code).first()
188 - if not customer:
189 - raise HTTPException(status_code=404, detail=f"Customer with customer_code {customer_code} not found")
190 - agents = session.query(Agents).filter(Agents.customer_code == customer_code).all()
191 - # Explode the agents list into a list of Agent objects
192 - agents = [AgentModel.parse_obj(agent.__dict__) for agent in agents]
193 - return AgentsResponse(agents=agents, success=True, message="Agents fetched successfully")
194 -
195 -
196 -# Retrieve the agents for the given customer_code then perform a healthcheck on them
197 -@customers_router.get(
198 - "/{customer_code}/agents/healthcheck/wazuh",
199 - response_model=AgentHealthCheckResponse,
200 - description="Get agents healthcheck for the given customer_code",
201 -)
202 -async def get_wazuh_agents_healthcheck(
203 - customer_code: str,
204 - minutes: int = Query(60, description="Number of minutes within which the agent should have been last seen to be considered healthy."),
205 - hours: int = Query(0, description="Number of hours within which the agent should have been last seen to be considered healthy."),
206 - days: int = Query(0, description="Number of days within which the agent should have been last seen to be considered healthy."),
207 -) -> AgentHealthCheckResponse:
208 - logger.info(f"Fetching agents for customer_code: {customer_code}")
209 - customer = session.query(Customers).filter(Customers.customer_code == customer_code).first()
210 - if not customer:
211 - raise HTTPException(status_code=404, detail=f"Customer with customer_code {customer_code} not found")
212 - agents = session.query(Agents).filter(Agents.customer_code == customer_code).all()
213 - # Explode the agents list into a list of Agent objects
214 - agents = [AgentModel.parse_obj(agent.__dict__) for agent in agents]
215 - time_criteria = TimeCriteriaModel(minutes=minutes, hours=hours, days=days)
216 - return wazuh_agents_healthcheck(agents, time_criteria)
217 -
218 -
219 -# Retrieve the agents for the given customer_code then perform a healthcheck on them
220 -@customers_router.get(
221 - "/{customer_code}/agents/healthcheck/velociraptor",
222 - response_model=AgentHealthCheckResponse,
223 - description="Get agents healthcheck for the given customer_code",
224 -)
225 -async def get_velociraptor_agents_healthcheck(
226 - customer_code: str,
227 - minutes: int = Query(60, description="Number of minutes within which the agent should have been last seen to be considered healthy."),
228 - hours: int = Query(0, description="Number of hours within which the agent should have been last seen to be considered healthy."),
229 - days: int = Query(0, description="Number of days within which the agent should have been last seen to be considered healthy."),
230 -) -> AgentHealthCheckResponse:
231 - logger.info(f"Fetching agents for customer_code: {customer_code}")
232 - customer = session.query(Customers).filter(Customers.customer_code == customer_code).first()
233 - if not customer:
234 - raise HTTPException(status_code=404, detail=f"Customer with customer_code {customer_code} not found")
235 - agents = session.query(Agents).filter(Agents.customer_code == customer_code).all()
236 - # Explode the agents list into a list of Agent objects
237 - agents = [AgentModel.parse_obj(agent.__dict__) for agent in agents]
238 - time_criteria = TimeCriteriaModel(minutes=minutes, hours=hours, days=days)
239 - return velociraptor_agents_healthcheck(agents, time_criteria)
backend/app/customers/schema/customers.py deleted
-121
@@ -1,121 +0,0 @@
1 -from datetime import datetime
2 -from typing import List
3 -from typing import Optional
4 -
5 -from pydantic import BaseModel
6 -from pydantic import Field
7 -
8 -
9 -class CustomerRequestBody(BaseModel):
10 - customer_code: str = Field(..., description="Unique code for the customer")
11 - customer_name: str = Field(..., description="Name of the customer")
12 - contact_last_name: str = Field(..., description="Last name of the contact person")
13 - contact_first_name: str = Field(..., description="First name of the contact person")
14 -
15 - parent_customer_code: Optional[str] = Field(None, description="Code for the parent customer")
16 - phone: Optional[str] = Field(None, description="Phone number")
17 - address_line1: Optional[str] = Field(None, description="First line of the address")
18 - address_line2: Optional[str] = Field(None, description="Second line of the address")
19 - city: Optional[str] = Field(None, description="City")
20 - state: Optional[str] = Field(None, description="State")
21 - postal_code: Optional[str] = Field(None, description="Postal Code")
22 - country: Optional[str] = Field(None, description="Country")
23 - customer_type: Optional[str] = Field(None, description="Type of the customer")
24 - logo_file: Optional[str] = Field(None, description="Logo file for the customer")
25 -
26 - class Config:
27 - schema_extra = {
28 - "example": {
29 - "customer_code": "CUST123",
30 - "customer_name": "Sample Customer",
31 - "contact_last_name": "Doe",
32 - "contact_first_name": "John",
33 - "phone": "123-456-7890",
34 - "address_line1": "123 Main St",
35 - "address_line2": "Apt 4",
36 - "city": "Anytown",
37 - "state": "CA",
38 - "postal_code": "12345",
39 - "country": "USA",
40 - "customer_type": "Enterprise",
41 - "logo_file": "logo.png",
42 - },
43 - }
44 -
45 -
46 -class CustomerResponse(BaseModel):
47 - customer: Optional[CustomerRequestBody]
48 - success: bool
49 - message: str
50 -
51 -
52 -class CustomersResponse(BaseModel):
53 - customers: list[CustomerRequestBody]
54 - success: bool
55 - message: str
56 -
57 -
58 -############# Customer Meta
59 -class CustomerMetaRequestBody(BaseModel):
60 - customer_meta_graylog_index: str = Field(..., description="Graylog index for the customer")
61 - customer_meta_graylog_stream: str = Field(..., description="Graylog stream for the customer")
62 - customer_meta_influx_org: str = Field(..., description="InfluxDB organization for the customer")
63 - customer_meta_grafana_org: str = Field(..., description="Grafana organization for the customer")
64 - customer_meta_wazuh_group: str = Field(..., description="Wazuh group for the customer")
65 - index_retention: int = Field(..., description="Index retention for the customer")
66 - wazuh_registration_port: int = Field(..., description="Wazuh registration port for the customer")
67 - wazuh_log_ingestion_port: int = Field(..., description="Wazuh log ingestion port for the customer")
68 -
69 - class Config:
70 - schema_extra = {
71 - "example": {
72 - "customer_meta_graylog_index": "graylog_index",
73 - "customer_meta_graylog_stream": "graylog_stream",
74 - "customer_meta_influx_org": "influx_org",
75 - "customer_meta_grafana_org": "grafana_org",
76 - "customer_meta_wazuh_group": "wazuh_group",
77 - "index_retention": 30,
78 - "wazuh_registration_port": 1514,
79 - "wazuh_log_ingestion_port": 1515,
80 - },
81 - }
82 -
83 -
84 -class CustomerMetaResponse(BaseModel):
85 - customer_meta: Optional[CustomerMetaRequestBody]
86 - success: bool
87 - message: str
88 -
89 -
90 -############# Customer Full Response
91 -class CustomerFullResponse(BaseModel):
92 - customer: Optional[CustomerRequestBody]
93 - customer_meta: Optional[CustomerMetaRequestBody]
94 - success: bool
95 - message: str
96 -
97 -
98 -############# Agent Model #############
99 -class AgentModel(BaseModel):
100 - id: Optional[int]
101 - os: Optional[str]
102 - label: Optional[str]
103 - wazuh_last_seen: Optional[datetime]
104 - velociraptor_last_seen: Optional[datetime]
105 - velociraptor_agent_version: Optional[str]
106 - ip_address: Optional[str]
107 - agent_id: Optional[str]
108 - hostname: Optional[str]
109 - critical_asset: Optional[bool]
110 - velociraptor_id: Optional[str]
111 - wazuh_agent_version: Optional[str]
112 - customer_code: Optional[str]
113 -
114 - class Config:
115 - orm_mode = True
116 -
117 -
118 -class AgentsResponse(BaseModel):
119 - agents: Optional[List[AgentModel]] = Field([], description="List of agents")
120 - success: bool
121 - message: str
backend/app/db/all_models.py deleted
-8
@@ -1,8 +0,0 @@
1 -# all_models.py
2 -from app.auth.models.users import User
3 -from app.connectors.models import Connectors
4 -from app.connectors.sublime.models.alerts import SublimeAlerts
5 -from app.connectors.wazuh_manager.models.rules import DisabledRule
6 -from app.db.universal_models import Agents
7 -from app.db.universal_models import Customers
8 -from app.db.universal_models import CustomersMeta
backend/app/db/db_populate.py deleted
-171
@@ -1,171 +0,0 @@
1 -from loguru import logger
2 -from sqlmodel import Session
3 -
4 -from app.auth.models.users import Role
5 -from app.connectors.models import Connectors
6 -
7 -
8 -def add_connectors_if_not_exist(session: Session):
9 - # List of connectors to add
10 - connector_list = [
11 - {
12 - "connector_name": "Wazuh-Indexer",
13 - "connector_type": "4.4.1",
14 - "connector_url": "https://ashwix01.socfortress.local:9200",
15 - "connector_username": "admin",
16 - "connector_password": "hmx7KPy15XPhJkgjlFrVgrWZ+Aid6QNm",
17 - "connector_api_key": None,
18 - "connector_configured": True,
19 - "connector_accepts_username_password": True,
20 - },
21 - {
22 - "connector_name": "Wazuh-Manager",
23 - "connector_type": "4.4.1",
24 - "connector_url": "https://ashwzhma.socfortress.local:55000",
25 - "connector_username": "wazuh-wui",
26 - "connector_password": "wazuh-wui",
27 - "connector_api_key": None,
28 - "connector_configured": True,
29 - "connector_accepts_username_password": True,
30 - },
31 - {
32 - "connector_name": "Graylog",
33 - "connector_type": "5.0.7",
34 - "connector_url": "http://ashgrl02.socfortress.local:9000",
35 - "connector_username": "socfortress_graylog_manager",
36 - "connector_password": "R{2PvE5TQkU7[xS$pX>fw>`y",
37 - "connector_api_key": None,
38 - "connector_configured": True,
39 - "connector_accepts_username_password": True,
40 - },
41 - {
42 - "connector_name": "Shuffle",
43 - "connector_type": "1.1.0",
44 - "connector_url": "https://ASHDKR02.socfortress.local:3443",
45 - "connector_username": "sting",
46 - "connector_password": "string",
47 - "connector_api_key": "bc5d1e18-6230-40f0-b032-6ed898c307c5",
48 - "connector_configured": True,
49 - "connector_accepts_api_key": True,
50 - },
51 - {
52 - "connector_name": "DFIR-IRIS",
53 - "connector_type": "2.0",
54 - "connector_url": "https://ashirs01.socfortress.local",
55 - "connector_username": None,
56 - "connector_password": None,
57 - "connector_api_key": "I3Hwvkpvdk8Z0XRFlyGm4WXGw8jksnEzvKNoD9BobtSQ2AgWmdo_p-pfmJCg_ev2cm8I-zgWzAfya3jLBWZ6qw",
58 - "connector_configured": True,
59 - "connector_accepts_api_key": True,
60 - },
61 - {
62 - "connector_name": "Velociraptor",
63 - "connector_type": "0.6.8",
64 - "connector_url": "https://ashvlo01.socfortress.local:8001",
65 - "connector_username": None,
66 - "connector_password": None,
67 - "connector_api_key": "C:\\Users\\walto\\Desktop\\GitHub\\CoPilot\\backend\\file-store\\api.config.yaml",
68 - "connector_configured": True,
69 - "connector_accepts_file": True,
70 - },
71 - {
72 - "connector_name": "RabbitMQ",
73 - "connector_type": "3",
74 - "connector_url": "ashdkr02.socfortress.local:5672",
75 - "connector_username": "guest",
76 - "connector_password": "guest",
77 - "connector_api_key": None,
78 - "connector_configured": True,
79 - "connector_accepts_username_password": True,
80 - },
81 - {
82 - "connector_name": "Sublime",
83 - "connector_type": "3",
84 - "connector_url": "http://ashdkr02.socfortress.local:8000",
85 - "connector_username": None,
86 - "connector_password": None,
87 - "connector_api_key": "7653trxhakxn4wxdh8bbatbvu97hm8fopos7wztzjrwfd12gf5i2kyebhvke9rt4",
88 - "connector_configured": True,
89 - "connector_accepts_api_key": True,
90 - },
91 - {
92 - "connector_name": "InfluxDB",
93 - "connector_type": "3",
94 - "connector_url": "http://ashdkr02.socfortress.local:8086",
95 - "connector_username": "SOCFortress",
96 - "connector_password": None,
97 - "connector_api_key": "gOLoFKucQXXd5d1rDx59YYktIz6OfrHIe4jRowJKZ8iB4IcZES8rOhRPaDEejEkahch8Ze2FiMzZxbQ9ZV8K6g==",
98 - "connector_configured": True,
99 - "connector_accepts_api_key": True,
100 - },
101 - {
102 - "connector_name": "AskSocfortress",
103 - "connector_type": "3",
104 - "connector_url": "https://api.socfortress.co/rule",
105 - "connector_username": None,
106 - "connector_password": None,
107 - "connector_api_key": "CkKmw1B9NM1hG669tC4sTazLm1HlRfSXVvMZkxa9",
108 - "connector_configured": True,
109 - "connector_accepts_api_key": True,
110 - },
111 - {
112 - "connector_name": "SocfortressThreatIntel",
113 - "connector_type": "3",
114 - "connector_url": "https://intel.socfortress.co/search",
115 - "connector_username": None,
116 - "connector_password": None,
117 - "connector_api_key": "ozH1jHp1zmacCePYrAZmxarJCGptcMth93a86Jq8",
118 - "connector_configured": True,
119 - "connector_accepts_api_key": True,
120 - },
121 - {
122 - "connector_name": "Cortex",
123 - "connector_type": "3",
124 - "connector_url": "http://ashvlo01.socfortress.local:9001",
125 - "connector_username": None,
126 - "connector_password": None,
127 - "connector_api_key": "+k/DvVYMEYURbc8sUdXA5/hW9VhJZV3v",
128 - "connector_configured": True,
129 - "connector_accepts_api_key": True,
130 - },
131 - ]
132 -
133 - for connector_data in connector_list:
134 - # Check if connector already exists in the database
135 - existing_connector = session.query(Connectors).filter_by(connector_name=connector_data["connector_name"]).first()
136 -
137 - if existing_connector is None:
138 - # If connector does not exist, create new connector entry
139 - new_connector = Connectors(**connector_data)
140 - session.add(new_connector)
141 - logger.info(f"Added new connector: {connector_data['connector_name']}")
142 -
143 - # Commit the changes if any new connectors were added
144 - session.commit()
145 -
146 -
147 -def add_roles_if_not_exist(session: Session):
148 - # List of roles to add
149 - role_list = [
150 - {
151 - "name": "admin",
152 - "description": "Administrator",
153 - },
154 - {
155 - "name": "analyst",
156 - "description": "SOC Analyst",
157 - },
158 - ]
159 -
160 - for role_data in role_list:
161 - # Check if role already exists in the database
162 - existing_role = session.query(Role).filter_by(name=role_data["name"]).first()
163 -
164 - if existing_role is None:
165 - # If role does not exist, create new role entry
166 - new_role = Role(**role_data)
167 - session.add(new_role)
168 - logger.info(f"Added new role: {role_data['name']}")
169 -
170 - # Commit the changes if any new roles were added
171 - session.commit()
backend/app/db/db_session.py deleted
-7
@@ -1,7 +0,0 @@
1 -from sqlmodel import Session
2 -from sqlmodel import create_engine
3 -
4 -from settings import SQLALCHEMY_DATABASE_URI
5 -
6 -engine = create_engine(SQLALCHEMY_DATABASE_URI)
7 -session = Session(bind=engine)
backend/app/db/db_setup.py deleted
-31
@@ -1,31 +0,0 @@
1 -from loguru import logger
2 -from sqlalchemy import inspect
3 -from sqlmodel import Session
4 -from sqlmodel import SQLModel
5 -
6 -# from app.db.all_models import *
7 -from app.db.db_populate import add_connectors_if_not_exist
8 -from app.db.db_populate import add_roles_if_not_exist
9 -
10 -
11 -def create_tables(engine):
12 - logger.info("Creating tables")
13 -
14 - # Create an inspector object based on the engine
15 - inspector = inspect(engine)
16 -
17 - # Get the names of all tables in the database
18 - existing_tables = inspector.get_table_names()
19 -
20 - # Loop through all your models (tables)
21 - for table in SQLModel.metadata.sorted_tables:
22 - if table.name not in existing_tables:
23 - # Only create the table if it doesn't exist
24 - table.create(bind=engine)
25 - logger.info(f"Table {table.name} created.")
26 -
27 - # After creating all tables, add connectors if they don't exist
28 - with Session(engine) as session:
29 - add_connectors_if_not_exist(session)
30 - add_roles_if_not_exist(session)
31 - session.commit()
backend/app/db/universal_models.py deleted
-122
@@ -1,122 +0,0 @@
1 -from datetime import datetime
2 -from typing import Optional
3 -
4 -from sqlmodel import Field
5 -from sqlmodel import Relationship
6 -from sqlmodel import SQLModel
7 -
8 -
9 -class Customers(SQLModel, table=True):
10 - id: Optional[int] = Field(primary_key=True)
11 - customer_code: str = Field(max_length=11, nullable=False)
12 - parent_customer_code: Optional[str] = Field(max_length=11)
13 - customer_name: str = Field(max_length=50, nullable=False)
14 - contact_last_name: Optional[str] = Field(max_length=50)
15 - contact_first_name: Optional[str] = Field(max_length=50)
16 - phone: Optional[str] = Field(max_length=50)
17 - address_line1: Optional[str] = Field(max_length=1024)
18 - address_line2: Optional[str] = Field(max_length=1024)
19 - city: Optional[str] = Field(max_length=50)
20 - state: Optional[str] = Field(max_length=50)
21 - postal_code: Optional[str] = Field(max_length=15)
22 - country: Optional[str] = Field(max_length=50)
23 - customer_type: Optional[str] = Field(max_length=50)
24 - logo_file: Optional[str] = Field(max_length=64)
25 - created_at: datetime = Field(default=datetime.utcnow())
26 -
27 - agents: list["Agents"] = Relationship(back_populates="customer")
28 - meta: Optional["CustomersMeta"] = Relationship(back_populates="customer")
29 -
30 - def update_from_model(self, customer):
31 - self.customer_code = customer.customer_code
32 - self.parent_customer_code = customer.parent_customer_code
33 - self.customer_name = customer.customer_name
34 - self.contact_last_name = customer.contact_last_name
35 - self.contact_first_name = customer.contact_first_name
36 - self.phone = customer.phone
37 - self.address_line1 = customer.address_line1
38 - self.address_line2 = customer.address_line2
39 - self.city = customer.city
40 - self.state = customer.state
41 - self.postal_code = customer.postal_code
42 - self.country = customer.country
43 - self.customer_type = customer.customer_type
44 - self.logo_file = customer.logo_file
45 -
46 -
47 -class CustomersMeta(SQLModel, table=True):
48 - id: Optional[int] = Field(primary_key=True)
49 - customer_code: str = Field(foreign_key="customers.customer_code", nullable=False)
50 - customer_name: str = Field(max_length=255)
51 - customer_meta_graylog_index: str = Field(max_length=1024)
52 - customer_meta_graylog_stream: str = Field(max_length=1024)
53 - customer_meta_influx_org: str = Field(max_length=1024)
54 - customer_meta_grafana_org: str = Field(max_length=1024)
55 - customer_meta_wazuh_group: str = Field(max_length=1024)
56 - index_retention: Optional[int] = Field()
57 - wazuh_registration_port: Optional[int] = Field()
58 - wazuh_log_ingestion_port: Optional[int] = Field()
59 -
60 - # Link back to Customers
61 - customer: Optional["Customers"] = Relationship(back_populates="meta")
62 -
63 - def update_from_model(self, customer_meta):
64 - if hasattr(customer_meta, "customer_code"):
65 - self.customer_code = customer_meta.customer_code
66 - if hasattr(customer_meta, "customer_name"):
67 - self.customer_name = customer_meta.customer_name
68 - self.customer_meta_graylog_index = customer_meta.customer_meta_graylog_index
69 - self.customer_meta_graylog_stream = customer_meta.customer_meta_graylog_stream
70 - self.customer_meta_influx_org = customer_meta.customer_meta_influx_org
71 - self.customer_meta_grafana_org = customer_meta.customer_meta_grafana_org
72 - self.customer_meta_wazuh_group = customer_meta.customer_meta_wazuh_group
73 - self.index_retention = customer_meta.index_retention
74 - self.wazuh_registration_port = customer_meta.wazuh_registration_port
75 - self.wazuh_log_ingestion_port = customer_meta.wazuh_log_ingestion_port
76 -
77 -
78 -class Agents(SQLModel, table=True):
79 - id: Optional[int] = Field(primary_key=True)
80 - agent_id: str = Field(index=True)
81 - ip_address: str = Field(max_length=256)
82 - os: str = Field(max_length=256)
83 - hostname: str = Field(max_length=256)
84 - label: str = Field(max_length=256)
85 - critical_asset: bool = Field(default=False)
86 - wazuh_last_seen: datetime
87 - velociraptor_id: str = Field(max_length=256)
88 - velociraptor_last_seen: datetime
89 - wazuh_agent_version: str = Field(max_length=256)
90 - velociraptor_agent_version: str = Field(max_length=256)
91 - customer_code: Optional[str] = Field(foreign_key="customers.customer_code")
92 -
93 - customer: Optional[Customers] = Relationship(back_populates="agents")
94 -
95 - @classmethod
96 - def create_from_model(cls, wazuh_agent, velociraptor_agent, customer_code):
97 - return cls(
98 - agent_id=wazuh_agent.agent_id,
99 - hostname=wazuh_agent.agent_name,
100 - ip_address=wazuh_agent.agent_ip,
101 - os=wazuh_agent.agent_os,
102 - label=wazuh_agent.agent_label,
103 - wazuh_last_seen=wazuh_agent.agent_last_seen_as_datetime,
104 - wazuh_agent_version=wazuh_agent.wazuh_agent_version,
105 - velociraptor_id=velociraptor_agent.client_id if velociraptor_agent.client_id else "n/a",
106 - velociraptor_last_seen=velociraptor_agent.client_last_seen_as_datetime,
107 - velociraptor_agent_version=velociraptor_agent.client_version,
108 - customer_code=customer_code,
109 - )
110 -
111 - def update_from_model(self, wazuh_agent, velociraptor_agent, customer_code):
112 - self.agent_id = wazuh_agent.agent_id
113 - self.hostname = wazuh_agent.agent_name
114 - self.ip_address = wazuh_agent.agent_ip
115 - self.os = wazuh_agent.agent_os
116 - self.label = wazuh_agent.agent_label
117 - self.wazuh_last_seen = wazuh_agent.agent_last_seen_as_datetime
118 - self.wazuh_agent_version = wazuh_agent.wazuh_agent_version
119 - self.velociraptor_id = velociraptor_agent.client_id if velociraptor_agent.client_id else "n/a"
120 - self.velociraptor_last_seen = velociraptor_agent.client_last_seen_as_datetime
121 - self.velociraptor_agent_version = velociraptor_agent.client_version
122 - self.customer_code = customer_code
backend/app/healthchecks/agents/routes/agents.py deleted
-94
@@ -1,94 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import HTTPException
3 -from fastapi import Query
4 -from loguru import logger
5 -from starlette.status import HTTP_401_UNAUTHORIZED
6 -
7 -from app.db.db_session import session
8 -from app.db.universal_models import Agents
9 -from app.healthchecks.agents.schema.agents import AgentHealthCheckResponse
10 -from app.healthchecks.agents.schema.agents import HostLogsSearchBody
11 -from app.healthchecks.agents.schema.agents import HostLogsSearchResponse
12 -from app.healthchecks.agents.schema.agents import TimeCriteriaModel
13 -from app.healthchecks.agents.services.agents import host_logs
14 -from app.healthchecks.agents.services.agents import velociraptor_agent_healthcheck
15 -from app.healthchecks.agents.services.agents import velociraptor_agents_healthcheck
16 -from app.healthchecks.agents.services.agents import wazuh_agent_healthcheck
17 -from app.healthchecks.agents.services.agents import wazuh_agents_healthcheck
18 -
19 -healtcheck_agents_router = APIRouter()
20 -
21 -
22 -def verify_admin(user):
23 - if not user.is_admin:
24 - raise HTTPException(status_code=HTTP_401_UNAUTHORIZED, detail="Unauthorized")
25 -
26 -
27 -@healtcheck_agents_router.get("/wazuh", response_model=AgentHealthCheckResponse, description="Get Wazuh agents healthcheck")
28 -async def get_wazuh_agent_healthcheck(
29 - minutes: int = Query(60, description="Number of minutes within which the agent should have been last seen to be considered healthy."),
30 - hours: int = Query(0, description="Number of hours within which the agent should have been last seen to be considered healthy."),
31 - days: int = Query(0, description="Number of days within which the agent should have been last seen to be considered healthy."),
32 -) -> AgentHealthCheckResponse:
33 - time_criteria = TimeCriteriaModel(minutes=minutes, hours=hours, days=days)
34 - agents = session.query(Agents).all()
35 - return wazuh_agents_healthcheck(agents, time_criteria)
36 -
37 -
38 -# Get single agent by agent_id
39 -@healtcheck_agents_router.get(
40 - "/wazuh/{agent_id}",
41 - response_model=AgentHealthCheckResponse,
42 - description="Get Wazuh agent healthcheck by agent_id",
43 -)
44 -async def get_wazuh_agent_healthcheck_by_agent_id(
45 - agent_id: str,
46 - minutes: int = Query(60, description="Number of minutes within which the agent should have been last seen to be considered healthy."),
47 - hours: int = Query(0, description="Number of hours within which the agent should have been last seen to be considered healthy."),
48 - days: int = Query(0, description="Number of days within which the agent should have been last seen to be considered healthy."),
49 -) -> AgentHealthCheckResponse:
50 - time_criteria = TimeCriteriaModel(minutes=minutes, hours=hours, days=days)
51 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
52 - if not agent:
53 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
54 - return wazuh_agent_healthcheck(agent, time_criteria)
55 -
56 -
57 -@healtcheck_agents_router.get("/velociraptor", response_model=AgentHealthCheckResponse, description="Get Velociraptor agents healthcheck")
58 -async def get_velociraptor_agent_healthcheck(
59 - minutes: int = Query(60, description="Number of minutes within which the agent should have been last seen to be considered healthy."),
60 - hours: int = Query(0, description="Number of hours within which the agent should have been last seen to be considered healthy."),
61 - days: int = Query(0, description="Number of days within which the agent should have been last seen to be considered healthy."),
62 -) -> AgentHealthCheckResponse:
63 - time_criteria = TimeCriteriaModel(minutes=minutes, hours=hours, days=days)
64 - agents = session.query(Agents).all()
65 - return velociraptor_agents_healthcheck(agents, time_criteria)
66 -
67 -
68 -# Get single agent by agent_id
69 -@healtcheck_agents_router.get(
70 - "/velociraptor/{agent_id}",
71 - response_model=AgentHealthCheckResponse,
72 - description="Get Velociraptor agent healthcheck by agent_id",
73 -)
74 -async def get_velociraptor_agent_healthcheck_by_agent_id(
75 - agent_id: str,
76 - minutes: int = Query(60, description="Number of minutes within which the agent should have been last seen to be considered healthy."),
77 - hours: int = Query(0, description="Number of hours within which the agent should have been last seen to be considered healthy."),
78 - days: int = Query(0, description="Number of days within which the agent should have been last seen to be considered healthy."),
79 -) -> AgentHealthCheckResponse:
80 - time_criteria = TimeCriteriaModel(minutes=minutes, hours=hours, days=days)
81 - agent = session.query(Agents).filter(Agents.agent_id == agent_id).first()
82 - if not agent:
83 - raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
84 - return velociraptor_agent_healthcheck(agent, time_criteria)
85 -
86 -
87 -@healtcheck_agents_router.post("/logs", response_model=HostLogsSearchResponse, description="Get host logs")
88 -async def get_host_logs(body: HostLogsSearchBody) -> HostLogsSearchResponse:
89 - logger.info(f"Received request to get host logs for {body.agent_name}")
90 - # Verify the agent exists
91 - agent = session.query(Agents).filter(Agents.hostname == body.agent_name).first()
92 - if not agent:
93 - raise HTTPException(status_code=404, detail=f"Agent with hostname {body.agent_name} not found")
94 - return host_logs(body)
backend/app/healthchecks/agents/schema/agents.py deleted
-102
@@ -1,102 +0,0 @@
1 -from datetime import datetime
2 -from typing import Any
3 -from typing import Dict
4 -from typing import List
5 -from typing import Optional
6 -
7 -from pydantic import BaseModel
8 -from pydantic import Field
9 -from pydantic import validator
10 -
11 -
12 -class AgentModel(BaseModel):
13 - id: Optional[int]
14 - os: Optional[str]
15 - label: Optional[str]
16 - wazuh_last_seen: Optional[datetime]
17 - velociraptor_last_seen: Optional[datetime]
18 - velociraptor_agent_version: Optional[str]
19 - ip_address: Optional[str]
20 - agent_id: Optional[str]
21 - hostname: Optional[str]
22 - critical_asset: Optional[bool]
23 - velociraptor_id: Optional[str]
24 - wazuh_agent_version: Optional[str]
25 - customer_code: Optional[str]
26 -
27 - class Config:
28 - orm_mode = True
29 -
30 -
31 -class ExtendedAgentModel(AgentModel):
32 - unhealthy_wazuh_agent: Optional[bool] = Field(None, description="Whether the agent is unhealthy in Wazuh")
33 - unhealthy_velociraptor_agent: Optional[bool] = Field(None, description="Whether the agent is unhealthy in Velociraptor")
34 - unhealthy_recent_logs_collected: Optional[bool] = Field(None, description="Whether the agent has not collected logs recently")
35 -
36 -
37 -class AgentHealthCheckResponse(BaseModel):
38 - healthy_wazuh_agents: Optional[List[ExtendedAgentModel]]
39 - unhealthy_wazuh_agents: Optional[List[ExtendedAgentModel]]
40 - healthy_velociraptor_agents: Optional[List[ExtendedAgentModel]]
41 - unhealthy_velociraptor_agents: Optional[List[ExtendedAgentModel]]
42 - healthy_recent_logs_collected: Optional[List[ExtendedAgentModel]]
43 - unhealthy_recent_logs_collected: Optional[List[ExtendedAgentModel]]
44 - message: str
45 - success: bool
46 -
47 -
48 -class TimeCriteriaModel(BaseModel):
49 - minutes: int = Field(60, description="Number of minutes within which the agent should have been last seen to be considered healthy.")
50 - hours: int = Field(0, description="Number of hours within which the agent should have been last seen to be considered healthy.")
51 - days: int = Field(0, description="Number of days within which the agent should have been last seen to be considered healthy.")
52 -
53 -
54 -########## Logs Schemas ##########
55 -
56 -
57 -class Log(BaseModel):
58 - index_name: str
59 - total_logs: int
60 - logs: Optional[List[Dict[str, Any]]] = Field([], description="The logs returned from the search.")
61 -
62 -
63 -class LogsSearchBody(BaseModel):
64 - size: int = Field(1, description="The number of logs to return.")
65 - timerange: str = Field("24h", description="The time range to search logs in.")
66 - log_field: str = Field("syslog_level", description="The field to search logs in.")
67 - log_value: str = Field("INFO", description="The value to search logs for.")
68 - timestamp_field: str = Field("timestamp_utc", description="The timestamp field to search logs in.")
69 -
70 - @validator("timerange")
71 - def validate_timerange(cls, value):
72 - if value[-1] not in ("h", "d", "w", "m"):
73 - raise ValueError("Invalid timerange format. The string should end with either 'h', 'd', 'w', or 'm'.")
74 -
75 - # Optionally, you can check that the prefix is a number
76 - if not value[:-1].isdigit():
77 - raise ValueError("Invalid timerange format. The string should start with a number.")
78 -
79 - return value
80 -
81 -
82 -class LogsSearchResponse(BaseModel):
83 - logs_summary: List[Log]
84 - success: bool
85 - message: str
86 -
87 -
88 -class CollectLogsResponse(BaseModel):
89 - logs: List[Dict[str, Any]]
90 - success: bool
91 - message: str
92 -
93 -
94 -class HostLogsSearchBody(LogsSearchBody):
95 - agent_name: str = Field(..., description="The name of the agent to search logs for.")
96 -
97 -
98 -class HostLogsSearchResponse(BaseModel):
99 - logs_summary: Optional[List[Log]] = Field([], description="The logs summary returned from the search.")
100 - healthy: bool = Field(False, description="Whether the host is healthy or not.")
101 - success: bool
102 - message: str
backend/app/healthchecks/agents/services/agents.py deleted
-211
@@ -1,211 +0,0 @@
1 -from datetime import datetime
2 -from datetime import timedelta
3 -from typing import Optional
4 -from typing import Type
5 -
6 -from fastapi import HTTPException
7 -from loguru import logger
8 -
9 -from app.connectors.wazuh_indexer.utils.universal import LogsQueryBuilder
10 -from app.connectors.wazuh_indexer.utils.universal import collect_indices
11 -from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
12 -from app.healthchecks.agents.schema.agents import AgentHealthCheckResponse
13 -from app.healthchecks.agents.schema.agents import AgentModel
14 -from app.healthchecks.agents.schema.agents import CollectLogsResponse
15 -from app.healthchecks.agents.schema.agents import ExtendedAgentModel
16 -from app.healthchecks.agents.schema.agents import HostLogsSearchBody
17 -from app.healthchecks.agents.schema.agents import HostLogsSearchResponse
18 -from app.healthchecks.agents.schema.agents import LogsSearchBody
19 -from app.healthchecks.agents.schema.agents import TimeCriteriaModel
20 -
21 -
22 -def is_wazuh_agent_unhealthy(agent: AgentModel, time_criteria: TimeCriteriaModel) -> ExtendedAgentModel:
23 - current_time = datetime.now()
24 - wazuh_last_seen = agent.wazuh_last_seen
25 -
26 - if wazuh_last_seen > current_time:
27 - logger.info(f"Agent {agent} has a wazuh_last_seen time in the future: {wazuh_last_seen}")
28 - return ExtendedAgentModel(**agent.dict(), unhealthy_wazuh_agent=True)
29 -
30 - # Calculate the total time delta based on the criteria
31 - total_minutes = time_criteria.minutes + time_criteria.hours * 60 + time_criteria.days * 24 * 60
32 - time_delta = timedelta(minutes=total_minutes)
33 -
34 - is_unhealthy = (current_time - wazuh_last_seen) > time_delta
35 - return ExtendedAgentModel(**agent.dict(), unhealthy_wazuh_agent=is_unhealthy)
36 -
37 -
38 -def is_velociraptor_agent_unhealthy(agent: AgentModel, time_criteria: TimeCriteriaModel) -> ExtendedAgentModel:
39 - current_time = datetime.now()
40 - velociraptor_last_seen = agent.velociraptor_last_seen
41 -
42 - if velociraptor_last_seen > current_time:
43 - logger.info(f"Agent {agent} has a velociraptor_last_seen time in the future: {velociraptor_last_seen}")
44 - return ExtendedAgentModel(**agent.dict(), unhealthy_velociraptor_agent=True)
45 -
46 - # Calculate the total time delta based on the criteria
47 - total_minutes = time_criteria.minutes + time_criteria.hours * 60 + time_criteria.days * 24 * 60
48 - time_delta = timedelta(minutes=total_minutes)
49 -
50 - is_unhealthy = (current_time - velociraptor_last_seen) > time_delta
51 - return ExtendedAgentModel(**agent.dict(), unhealthy_velociraptor_agent=is_unhealthy)
52 -
53 -
54 -def wazuh_agents_healthcheck(agents: list, time_criteria: TimeCriteriaModel) -> AgentHealthCheckResponse:
55 - healthy_wazuh_agents = []
56 - unhealthy_wazuh_agents = []
57 - for agent in agents:
58 - # If agent_id is `000` skip it because this is the Wazuh manager
59 - if agent.agent_id == "000":
60 - continue
61 - logger.info(f"Checking agent {agent} for health")
62 - extended_agent = is_wazuh_agent_unhealthy(agent, time_criteria)
63 - logger.info(f"Extended agent: {extended_agent}")
64 - if extended_agent.unhealthy_wazuh_agent:
65 - unhealthy_wazuh_agents.append(extended_agent)
66 - else:
67 - healthy_wazuh_agents.append(extended_agent)
68 -
69 - return AgentHealthCheckResponse(
70 - healthy_wazuh_agents=healthy_wazuh_agents,
71 - unhealthy_wazuh_agents=unhealthy_wazuh_agents,
72 - success=True,
73 - message="Wazuh agent healthcheck fetched successfully",
74 - )
75 -
76 -
77 -def wazuh_agent_healthcheck(agent: AgentModel, time_criteria: TimeCriteriaModel) -> AgentHealthCheckResponse:
78 - extended_agent = is_wazuh_agent_unhealthy(agent, time_criteria)
79 - if extended_agent.unhealthy_wazuh_agent:
80 - return AgentHealthCheckResponse(
81 - healthy_wazuh_agents=[],
82 - unhealthy_wazuh_agents=[extended_agent],
83 - success=True,
84 - message="Wazuh agent healthcheck fetched successfully",
85 - )
86 - else:
87 - return AgentHealthCheckResponse(
88 - healthy_wazuh_agents=[extended_agent],
89 - unhealthy_wazuh_agents=[],
90 - success=True,
91 - message="Wazuh agent healthcheck fetched successfully",
92 - )
93 -
94 -
95 -def velociraptor_agents_healthcheck(agents: list, time_criteria: TimeCriteriaModel) -> AgentHealthCheckResponse:
96 - healthy_velociraptor_agents = []
97 - unhealthy_velociraptor_agents = []
98 - for agent in agents:
99 - # If agent_id is `000` skip it because this is the Wazuh manager
100 - if agent.agent_id == "000":
101 - continue
102 - logger.info(f"Checking agent {agent} for health")
103 - extended_agent = is_velociraptor_agent_unhealthy(agent, time_criteria)
104 - logger.info(f"Extended agent: {extended_agent}")
105 - if extended_agent.unhealthy_velociraptor_agent:
106 - unhealthy_velociraptor_agents.append(extended_agent)
107 - else:
108 - healthy_velociraptor_agents.append(extended_agent)
109 -
110 - return AgentHealthCheckResponse(
111 - healthy_velociraptor_agents=healthy_velociraptor_agents,
112 - unhealthy_velociraptor_agents=unhealthy_velociraptor_agents,
113 - success=True,
114 - message="Velociraptor agent healthcheck fetched successfully",
115 - )
116 -
117 -
118 -def velociraptor_agent_healthcheck(agent: AgentModel, time_criteria: TimeCriteriaModel) -> AgentHealthCheckResponse:
119 - extended_agent = is_velociraptor_agent_unhealthy(agent, time_criteria)
120 - if extended_agent.unhealthy_velociraptor_agent:
121 - return AgentHealthCheckResponse(
122 - healthy_velociraptor_agents=[],
123 - unhealthy_velociraptor_agents=[extended_agent],
124 - success=True,
125 - message="Velociraptor agent healthcheck fetched successfully",
126 - )
127 - else:
128 - return AgentHealthCheckResponse(
129 - healthy_velociraptor_agents=[extended_agent],
130 - unhealthy_velociraptor_agents=[],
131 - success=True,
132 - message="Velociraptor agent healthcheck fetched successfully",
133 - )
134 -
135 -
136 -def host_logs(search_body: HostLogsSearchBody) -> HostLogsSearchResponse:
137 - result = get_logs_generic(search_body, is_host_specific=True)
138 - logger.info(f"Host logs search result: {result}")
139 -
140 - # Initialize variable to keep track of total logs
141 - total_logs = 0
142 -
143 - # Loop through each item in logs_summary to count total logs
144 - for log_summary in result["logs_summary"]:
145 - total_logs += log_summary["total_logs"]
146 -
147 - # Check if there are any logs
148 - if total_logs > 0:
149 - return HostLogsSearchResponse(
150 - success=True,
151 - healthy=True,
152 - message=f"Host is healthy. At least one log was found within the specified time range of {search_body.timerange}",
153 - )
154 - else:
155 - return HostLogsSearchResponse(
156 - success=True,
157 - healthy=False,
158 - message=f"Host is unhealthy. No logs were found within the specified time range of {search_body.timerange}",
159 - )
160 -
161 -
162 -def get_logs_generic(search_body: Type[LogsSearchBody], is_host_specific: bool = False, index_name: Optional[str] = None):
163 - logger.info(f"Collecting Wazuh Indexer alerts for host {search_body.agent_name if is_host_specific else ''}")
164 - logs_summary = []
165 - indices = collect_indices()
166 - index_list = [index_name] if index_name else indices.indices_list # Use the provided index_name or get all indices
167 -
168 - for index_name in index_list:
169 - try:
170 - logs = collect_logs_generic(index_name, body=search_body, is_host_specific=is_host_specific)
171 - if logs.success and len(logs.logs) > 0:
172 - logs_summary.append(
173 - {
174 - "index_name": index_name,
175 - "total_logs": len(logs.logs),
176 - "logs": logs.logs,
177 - },
178 - )
179 - break # Only collect logs from the first index that has logs
180 - except HTTPException as e:
181 - logger.warning(f"An error occurred while processing index {index_name}: {e.detail}")
182 -
183 - if len(logs_summary) == 0:
184 - message = "No logs found"
185 - else:
186 - message = f"Succesfully collected top {search_body.size} logs for each index"
187 -
188 - return {"logs_summary": logs_summary, "success": len(logs_summary) > 0, "message": message}
189 -
190 -
191 -def collect_logs_generic(index_name: str, body: LogsSearchBody, is_host_specific: bool = False) -> CollectLogsResponse:
192 - es_client = create_wazuh_indexer_client("Wazuh-Indexer")
193 - query_builder = LogsQueryBuilder()
194 - query_builder.add_time_range(timerange=body.timerange, timestamp_field=body.timestamp_field)
195 - query_builder.add_matches(matches=[(body.log_field, body.log_value)])
196 - query_builder.add_sort(body.timestamp_field)
197 -
198 - if is_host_specific:
199 - query_builder.add_match_phrase(matches=[("agent_name", body.agent_name)])
200 -
201 - query = query_builder.build()
202 -
203 - try:
204 - logs = es_client.search(index=index_name, body=query, size=body.size)
205 - logger.info(f"logs collected: {logs}")
206 - logs_list = [log for log in logs["hits"]["hits"]]
207 - logger.info(f"logs collected: {logs_list}")
208 - return CollectLogsResponse(logs=logs_list, success=True, message="logs collected successfully")
209 - except Exception as e:
210 - logger.debug(f"Failed to collect logs: {e}")
211 - return CollectLogsResponse(logs=[], success=False, message=f"Failed to collect logs: {e}")
backend/app/integrations/alert_escalation/routes/general_alert.py deleted
-14
@@ -1,14 +0,0 @@
1 -from fastapi import APIRouter
2 -from loguru import logger
3 -
4 -from app.integrations.alert_escalation.schema.general_alert import CreateAlertRequest
5 -from app.integrations.alert_escalation.schema.general_alert import CreateAlertResponse
6 -from app.integrations.alert_escalation.services.general_alert import create_alert
7 -
8 -integration_general_alerts_router = APIRouter()
9 -
10 -
11 -@integration_general_alerts_router.post("/create", response_model=CreateAlertResponse, description="Create an alert in IRIS")
12 -async def create_alert_route(create_alert_request: CreateAlertRequest) -> CreateAlertResponse:
13 - logger.info(f"Creating alert {create_alert_request.alert_id} in IRIS")
14 - return create_alert(create_alert_request)
backend/app/integrations/alert_escalation/schema/general_alert.py deleted
-115
@@ -1,115 +0,0 @@
1 -from enum import Enum
2 -from typing import Any
3 -from typing import Dict
4 -from typing import List
5 -from typing import Optional
6 -
7 -from pydantic import BaseModel
8 -from pydantic import Extra
9 -from pydantic import Field
10 -
11 -
12 -class ValidIocFields(Enum):
13 - MISP_VALUE = "misp_value"
14 - OPENCTI_VALUE = "opencti_value"
15 - THREAT_INTEL_VALUE = "threat_intel_value"
16 -
17 -
18 -class CreateAlertRequest(BaseModel):
19 - index_name: str = Field(..., description="The name of the index to search alerts for.")
20 - alert_id: str = Field(..., description="The alert id to create.")
21 -
22 -
23 -class CreateAlertResponse(BaseModel):
24 - success: bool
25 - message: str
26 - alert_id: int = Field(..., description="The alert id as created in IRIS.")
27 -
28 -
29 -class GenericSourceModel(BaseModel):
30 - agent_name: str = Field(..., description="The name of the agent.")
31 - agent_id: str = Field(..., description="The id of the agent.")
32 - agent_labels_customer: str = Field(..., description="The customer of the agent.")
33 - rule_id: str = Field(..., description="The id of the rule.")
34 - rule_level: int = Field(..., description="The level of the rule.")
35 - rule_description: str = Field(..., description="The description of the rule.")
36 - timestamp: str = Field(..., description="The timestamp of the alert.")
37 - timestamp_utc: Optional[str] = Field(..., description="The UTC timestamp of the alert.")
38 -
39 - class Config:
40 - extra = Extra.allow
41 -
42 -
43 -class GenericAlertModel(BaseModel):
44 - _index: str
45 - _id: str
46 - _version: int
47 - _source: GenericSourceModel # Nested model
48 - asset_type_id: Optional[int] = Field(
49 - None,
50 - description="The asset type id of the alert which is needed for when we add the asset to IRIS.",
51 - )
52 - ioc_value: Optional[str] = Field(None, description="The IoC value of the alert which is needed for when we add the IoC to IRIS.")
53 - ioc_type: Optional[str] = Field(None, description="The IoC type of the alert which is needed for when we add the IoC to IRIS.")
54 -
55 - class Config:
56 - extra = Extra.allow
57 -
58 -
59 -# Sample data from `get_single_alert_details`
60 -sample_data = {
61 - "_index": "some_index",
62 - "_id": "some_id",
63 - "_version": 1,
64 - "_source": {
65 - "agent_name": "some_agent_name",
66 - "agent_id": "some_agent_id",
67 - # ... other fields
68 - },
69 - # ... other fields
70 -}
71 -
72 -
73 -########### Create Alerts Schemas ###########
74 -class IrisAsset(BaseModel):
75 - asset_name: str = Field(..., description="Name of the asset", example="Server01")
76 - asset_ip: str = Field(..., description="IP address of the asset", example="192.168.1.1")
77 - asset_description: str = Field(..., description="Description of the asset", example="Windows Server")
78 - asset_type_id: int = Field(..., description="Type ID of the asset", example=1)
79 -
80 -
81 -class IrisIoc(BaseModel):
82 - ioc_value: str = Field(..., description="Value of the IoC", example="www.google.com")
83 - ioc_description: str = Field(..., description="Description of the IoC", example="Google")
84 - ioc_tlp_id: int = Field(1, description="TLP ID of the IoC", example=1)
85 - ioc_type_id: int = Field(20, description="Type ID of the IoC", example=20)
86 -
87 -
88 -class IrisAlertContext(BaseModel):
89 - alert_id: str = Field(..., description="ID of the alert", example="123")
90 - alert_name: str = Field(..., description="Name of the alert", example="Intrusion Detected")
91 - alert_level: int = Field(..., description="Severity level of the alert", example=3)
92 - rule_id: str = Field(..., description="ID of the rule that triggered the alert", example="2001")
93 - asset_name: str = Field(..., description="Name of the affected asset", example="Server01")
94 - asset_ip: str = Field(..., description="IP address of the affected asset", example="192.168.1.1")
95 - asset_type: int = Field(..., description="Type ID of the affected asset", example=1)
96 - process_id: Optional[str] = Field("No process ID found", description="Process ID involved in the alert", example="4567")
97 - rule_mitre_id: Optional[str] = Field("n/a", description="MITRE ATT&CK ID of the rule", example="T1234")
98 - rule_mitre_tactic: Optional[str] = Field("n/a", description="MITRE ATT&CK Tactic", example="Execution")
99 - rule_mitre_technique: Optional[str] = Field("n/a", description="MITRE ATT&CK Technique", example="Scripting")
100 -
101 -
102 -class IrisAlertPayload(BaseModel):
103 - alert_title: str = Field(..., description="Title of the alert", example="Intrusion Detected")
104 - alert_description: str = Field(..., description="Description of the alert", example="Intrusion Detected by Firewall")
105 - alert_source: str = Field(..., description="Source of the alert", example="Wazuh")
106 - assets: List[IrisAsset] = Field(..., description="List of affected assets")
107 - alert_status_id: int = Field(..., description="Status ID of the alert", example=3)
108 - alert_severity_id: int = Field(..., description="Severity ID of the alert", example=5)
109 - alert_customer_id: int = Field(..., description="Customer ID related to the alert", example=1)
110 - alert_source_content: Dict[str, Any] = Field(..., description="Original content from the alert source")
111 - alert_context: IrisAlertContext = Field(..., description="Contextual information about the alert")
112 - alert_iocs: Optional[List[IrisIoc]] = Field(None, description="List of IoCs related to the alert")
113 -
114 - def to_dict(self):
115 - return self.dict(exclude_none=True)
backend/app/integrations/alert_escalation/services/general_alert.py deleted
-123
@@ -1,123 +0,0 @@
1 -from typing import Optional
2 -from typing import Set
3 -
4 -from fastapi import HTTPException
5 -from loguru import logger
6 -
7 -from app.connectors.dfir_iris.utils.universal import fetch_and_validate_data
8 -from app.connectors.dfir_iris.utils.universal import initialize_client_and_alert
9 -from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
10 -from app.integrations.alert_escalation.schema.general_alert import CreateAlertRequest
11 -from app.integrations.alert_escalation.schema.general_alert import CreateAlertResponse
12 -from app.integrations.alert_escalation.schema.general_alert import GenericAlertModel
13 -from app.integrations.alert_escalation.schema.general_alert import GenericSourceModel
14 -from app.integrations.alert_escalation.schema.general_alert import IrisAlertContext
15 -from app.integrations.alert_escalation.schema.general_alert import IrisAlertPayload
16 -from app.integrations.alert_escalation.schema.general_alert import IrisAsset
17 -from app.integrations.alert_escalation.schema.general_alert import IrisIoc
18 -from app.integrations.alert_escalation.schema.general_alert import ValidIocFields
19 -from app.integrations.alert_escalation.utils.universal import get_agent_data
20 -from app.integrations.alert_escalation.utils.universal import get_asset_type_id
21 -from app.integrations.alert_escalation.utils.universal import validate_ioc_type
22 -
23 -
24 -def valid_ioc_fields() -> Set[str]:
25 - """
26 - Getter for the set of valid IoC fields.
27 - Returns
28 - -------
29 - Set[str]
30 - The set of valid IoC fields.
31 - """
32 - return {field.value for field in ValidIocFields}
33 -
34 -
35 -def get_single_alert_details(alert_details: CreateAlertRequest) -> GenericAlertModel:
36 - logger.info(f"Fetching alert details for alert {alert_details.alert_id} in index {alert_details.index_name}")
37 - es_client = create_wazuh_indexer_client("Wazuh-Indexer")
38 - try:
39 - alert = es_client.get(index=alert_details.index_name, id=alert_details.alert_id)
40 - source_model = GenericSourceModel(**alert["_source"])
41 - return GenericAlertModel(_source=source_model, _id=alert["_id"], _index=alert["_index"], _version=alert["_version"])
42 - except Exception as e:
43 - logger.debug(f"Failed to collect alert details: {e}")
44 - raise HTTPException(status_code=400, detail=f"Failed to collect alert details: {e}")
45 -
46 -
47 -def build_ioc_payload(alert_details: GenericAlertModel) -> Optional[IrisIoc]:
48 - for field in valid_ioc_fields():
49 - if hasattr(alert_details._source, field):
50 - ioc_value = getattr(alert_details._source, field)
51 - ioc_type = validate_ioc_type(ioc_value=ioc_value)
52 - return IrisIoc(ioc_value=ioc_value, ioc_description="IoC found in alert", ioc_tlp_id=1, ioc_type_id=ioc_type)
53 - return None
54 -
55 -
56 -def build_asset_payload(agent_data, alert_details) -> IrisAsset:
57 - return IrisAsset(
58 - asset_name=agent_data.hostname,
59 - asset_ip=agent_data.ip_address,
60 - asset_description=agent_data.os,
61 - asset_type_id=alert_details.asset_type_id,
62 - )
63 -
64 -
65 -def build_alert_context_payload(alert_details: GenericAlertModel, agent_data) -> IrisAlertContext:
66 - return IrisAlertContext(
67 - alert_id=alert_details._id,
68 - alert_name=alert_details._source.rule_description,
69 - alert_level=alert_details._source.rule_level,
70 - rule_id=alert_details._source.rule_id,
71 - asset_name=agent_data.hostname,
72 - asset_ip=agent_data.ip_address,
73 - asset_type=alert_details.asset_type_id,
74 - process_id=getattr(alert_details._source, "process_id", "No process id found"),
75 - rule_mitre_id=getattr(alert_details._source, "rule_mitre_id", "No rule mitre id found"),
76 - rule_mitre_tactic=getattr(alert_details._source, "rule_mitre_tactic", "No rule mitre tactic found"),
77 - rule_mitre_technique=getattr(alert_details._source, "rule_mitre_technique", "No rule mitre technique found"),
78 - )
79 -
80 -
81 -def build_alert_payload(alert_details: GenericAlertModel, agent_data, ioc_payload: Optional[IrisIoc]) -> IrisAlertPayload:
82 - asset_payload = build_asset_payload(agent_data, alert_details)
83 - context_payload = build_alert_context_payload(alert_details, agent_data)
84 - if ioc_payload:
85 - logger.info(f"Alert has IoC: {ioc_payload}")
86 - return IrisAlertPayload(
87 - alert_title=alert_details._source.rule_description,
88 - alert_description=alert_details._source.rule_description,
89 - alert_source="CoPilot",
90 - assets=[asset_payload],
91 - alert_status_id=3,
92 - alert_severity_id=5,
93 - alert_customer_id=1,
94 - alert_source_content=alert_details._source,
95 - alert_context=context_payload,
96 - alert_iocs=[ioc_payload],
97 - )
98 - else:
99 - logger.info("Alert does not have IoC")
100 - return IrisAlertPayload(
101 - alert_title=alert_details._source.rule_description,
102 - alert_description=alert_details._source.rule_description,
103 - alert_source="CoPilot",
104 - assets=[asset_payload],
105 - alert_status_id=3,
106 - alert_severity_id=5,
107 - alert_customer_id=1,
108 - alert_source_content=alert_details._source,
109 - alert_context=context_payload,
110 - )
111 -
112 -
113 -def create_alert(alert: CreateAlertRequest) -> CreateAlertResponse:
114 - logger.info(f"Creating alert {alert.alert_id} in IRIS")
115 - alert_details = get_single_alert_details(alert_details=alert)
116 - agent_data = get_agent_data(agent_id=alert_details._source.agent_id)
117 - alert_details.asset_type_id = get_asset_type_id(os=agent_data.os)
118 - ioc_payload = build_ioc_payload(alert_details)
119 - iris_alert_payload = build_alert_payload(alert_details, agent_data, ioc_payload)
120 - client, alert = initialize_client_and_alert("DFIR-IRIS")
121 - result = fetch_and_validate_data(client, alert.add_alert, iris_alert_payload.to_dict())
122 - alert_id = result["data"]["alert_id"]
123 - return CreateAlertResponse(alert_id=alert_id, success=True, message=f"Alert {alert_id} created successfully")
backend/app/integrations/alert_escalation/utils/universal.py deleted
-406
@@ -1,406 +0,0 @@
1 -import ipaddress
2 -import re
3 -from abc import ABC
4 -from typing import Any
5 -from typing import Dict
6 -from typing import Optional
7 -from typing import Union
8 -
9 -import regex
10 -from elasticsearch7 import Elasticsearch
11 -from fastapi import HTTPException
12 -from loguru import logger
13 -
14 -from app.connectors.utils import get_connector_info_from_db
15 -from app.db.all_models import Agents
16 -from app.db.db_session import session
17 -from app.healthchecks.agents.schema.agents import AgentModel
18 -
19 -
20 -#################### ! DFIR IRIS ASSET VALIDATOR ! ####################
21 -class AssetValidator(ABC):
22 - """
23 - Base class for asset validators.
24 -
25 - Attributes:
26 - os (str): The OS to be validated.
27 - """
28 -
29 - ASSET_TYPE_ID: int = 1
30 -
31 - def __init__(self, os: str) -> None:
32 - """
33 - Initialize a Validator.
34 -
35 - Args:
36 - os (str): The OS to be validated.
37 - """
38 - self.os = os.lower()
39 -
40 - def validate(self) -> Dict[str, Union[bool, str, int]]:
41 - """
42 - Validate the OS.
43 -
44 - If the OS matches the type of this validator,
45 - the method returns a dictionary indicating success, the matching message, and the asset type id.
46 -
47 - Returns:
48 - Dict[str, Union[bool, str, int]]: The validation result.
49 - """
50 - raise NotImplementedError
51 -
52 -
53 -class WindowsAssetValidator(AssetValidator):
54 - """
55 - Class to check if an OS is Windows.
56 - """
57 -
58 - ASSET_TYPE_ID = 9
59 -
60 - def validate(self) -> Dict[str, Union[bool, str, int]]:
61 - if "windows" in self.os:
62 - return {
63 - "success": True,
64 - "message": f"{self.os} is a valid Windows OS.",
65 - "asset_type_id": self.ASSET_TYPE_ID,
66 - }
67 - else:
68 - return {
69 - "success": False,
70 - "message": f"{self.os} is not a Windows OS.",
71 - "asset_type_id": self.ASSET_TYPE_ID,
72 - }
73 -
74 -
75 -class LinuxAssetValidator(AssetValidator):
76 - """
77 - Class to check if an OS is Linux.
78 - """
79 -
80 - ASSET_TYPE_ID = 4
81 -
82 - def validate(self) -> Dict[str, Union[bool, str, int]]:
83 - if "linux" in self.os:
84 - return {
85 - "success": True,
86 - "message": f"{self.os} is a valid Linux OS.",
87 - "asset_type_id": self.ASSET_TYPE_ID,
88 - }
89 - else:
90 - return {
91 - "success": False,
92 - "message": f"{self.os} is not a Linux OS.",
93 - "asset_type_id": self.ASSET_TYPE_ID,
94 - }
95 -
96 -
97 -class FirewallAssetValidator(AssetValidator):
98 - """
99 - Class to check if an OS is Firewall.
100 - """
101 -
102 - ASSET_TYPE_ID = 2
103 -
104 - def validate(self) -> Dict[str, Union[bool, str, int]]:
105 - if "firewall" in self.os:
106 - return {
107 - "success": True,
108 - "message": f"{self.os} is a valid Firewall OS.",
109 - "asset_type_id": self.ASSET_TYPE_ID,
110 - }
111 - else:
112 - return {
113 - "success": False,
114 - "message": f"{self.os} is not a Firewall OS.",
115 - "asset_type_id": self.ASSET_TYPE_ID,
116 - }
117 -
118 -
119 -class UbuntuAssetValidator(AssetValidator):
120 - """
121 - Class to check if an OS is Ubuntu.
122 - """
123 -
124 - ASSET_TYPE_ID = 4
125 -
126 - def validate(self) -> Dict[str, Union[bool, str, int]]:
127 - if "ubuntu" in self.os:
128 - return {
129 - "success": True,
130 - "message": f"{self.os} is a valid Ubuntu OS.",
131 - "asset_type_id": self.ASSET_TYPE_ID,
132 - }
133 - else:
134 - return {
135 - "success": False,
136 - "message": f"{self.os} is not an Ubuntu OS.",
137 - "asset_type_id": self.ASSET_TYPE_ID,
138 - }
139 -
140 -
141 -class AssetTypeResolver:
142 - """
143 - Class to iterate over asset validators and return the successful validator's asset type id.
144 - """
145 -
146 - def __init__(self, os: str):
147 - """
148 - Initialize AssetTypeResolver.
149 -
150 - Args:
151 - os (str): The OS to be validated.
152 - """
153 - self.os = os
154 - self.validators = [
155 - WindowsAssetValidator,
156 - LinuxAssetValidator,
157 - FirewallAssetValidator,
158 - UbuntuAssetValidator,
159 - ]
160 -
161 - def get_asset_type_id(self) -> int:
162 - """
163 - Iterate over validators and return the successful validator's asset type id.
164 -
165 - Returns:
166 - int: The asset type id.
167 - """
168 - for Validator in self.validators:
169 - validator = Validator(self.os)
170 - result = validator.validate()
171 - if result["success"] is True:
172 - return result["asset_type_id"]
173 -
174 - # Return default asset type id (1) if no validators succeed
175 - return 1
176 -
177 -
178 -#################### ! DFIR IRIS ASSET VALIDATOR END ! ####################
179 -
180 -
181 -#################### ! DFIR IRIS IOC VALIDATOR ! ##########################
182 -
183 -
184 -class IoCValidator(ABC):
185 - """
186 - Base class for validators.
187 -
188 - Attributes:
189 - value (str): The value to be validated.
190 - """
191 -
192 - PATTERN: Optional[str] = None # type: ignore
193 - IOC_TYPE: Optional[int] = None # type: ignore
194 -
195 - def __init__(self, value: str) -> None:
196 - """
197 - Initialize a Validator.
198 -
199 - Args:
200 - value (str): The value to be validated.
201 - """
202 - self.value = value
203 -
204 - def validate(self) -> Dict[str, Union[bool, str, int]]:
205 - """
206 - Validate the value.
207 -
208 - If the value matches the pattern,
209 - the method returns a dictionary indicating success, the matching message, and the IOC type.
210 -
211 - Returns:
212 - Dict[str, Union[bool, str, int]]: The validation result.
213 - """
214 - logger.info(f"Validating {self.value} against {self.PATTERN}.")
215 - if self.PATTERN and regex.match(self.PATTERN, self.value, re.IGNORECASE):
216 - return {
217 - "success": True,
218 - "message": f"{self.value} matches the pattern.",
219 - "ioc_type": self.IOC_TYPE,
220 - }
221 - else:
222 - return {
223 - "success": False,
224 - "message": f"{self.value} does not match the pattern.",
225 - "ioc_type": self.IOC_TYPE,
226 - }
227 -
228 -
229 -class IPv4AddressValidator(IoCValidator):
230 - """
231 - Class to check if a string is a valid IPv4 address.
232 - """
233 -
234 - IOC_TYPE = 76
235 -
236 - def validate(self) -> Dict[str, Union[bool, str, int]]:
237 - """
238 - Validate if the given value is a valid IPv4 address.
239 -
240 - Returns:
241 - dict: A dictionary containing success status, message, and the associated IoC type.
242 - """
243 - try:
244 - # if the value is like this `162.159.133.233|443` strip the port
245 - if "|" in self.value:
246 - self.value = self.value.split("|")[0]
247 - logger.info(f"Validating {self.value} as an IPv4 address.")
248 - ipaddress.IPv4Address(self.value)
249 - return {
250 - "success": True,
251 - "message": f"{self.value} is a valid IPv4 address.",
252 - "ioc_type": self.IOC_TYPE,
253 - }
254 - except ValueError:
255 - return {
256 - "success": False,
257 - "message": f"{self.value} is not a valid IPv4 address.",
258 - "ioc_type": self.IOC_TYPE,
259 - }
260 -
261 -
262 -class HashValidator(IoCValidator):
263 - """
264 - Class to check if a string is a valid SHA256 hash.
265 - """
266 -
267 - PATTERN = r"^[a-fA-F\d]{64}$"
268 - IOC_TYPE = 113
269 -
270 -
271 -class DomainValidator(IoCValidator):
272 - """
273 - Class to check if a string is a valid domain name.
274 - """
275 -
276 - PATTERN = r"^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$"
277 - IOC_TYPE = 20
278 -
279 -
280 -#################### ! DFIR IRIS IOC VALIDATOR END ! ##########################
281 -
282 -
283 -def verify_wazuh_indexer_credentials(attributes: Dict[str, Any]) -> Dict[str, Any]:
284 - """
285 - Verifies the connection to Wazuh Indexer service.
286 -
287 - Returns:
288 - dict: A dictionary containing 'connectionSuccessful' status and 'authToken' if the connection is successful.
289 - """
290 - logger.info(f"Verifying the wazuh-indexer connection to {attributes['connector_url']}")
291 -
292 - try:
293 - es = Elasticsearch(
294 - [attributes["connector_url"]],
295 - http_auth=(attributes["connector_username"], attributes["connector_password"]),
296 - verify_certs=False,
297 - timeout=15,
298 - max_retries=10,
299 - retry_on_timeout=False,
300 - )
301 - es.cluster.health()
302 - logger.debug("Wazuh Indexer connection successful")
303 - return {"connectionSuccessful": True, "message": "Wazuh Indexer connection successful"}
304 - except Exception as e:
305 - logger.error(f"Connection to {attributes['connector_url']} failed with error: {e}")
306 - return {"connectionSuccessful": False, "message": f"Connection to {attributes['connector_url']} failed with error: {e}"}
307 -
308 -
309 -def verify_wazuh_indexer_connection(connector_name: str) -> str:
310 - """
311 - Returns the authentication token for the Wazuh Indexer service.
312 -
313 - Returns:
314 - str: Authentication token for the Wazuh Indexer service.
315 - """
316 - attributes = get_connector_info_from_db(connector_name)
317 - if attributes is None:
318 - logger.error("No Wazuh Indexer connector found in the database")
319 - return None
320 - return verify_wazuh_indexer_credentials(attributes)
321 -
322 -
323 -def create_wazuh_indexer_client(connector_name: str) -> Elasticsearch:
324 - """
325 - Returns an Elasticsearch client for the Wazuh Indexer service.
326 -
327 - Returns:
328 - Elasticsearch: Elasticsearch client for the Wazuh Indexer service.
329 - """
330 - attributes = get_connector_info_from_db(connector_name)
331 - if attributes is None:
332 - logger.error("No Wazuh Indexer connector found in the database")
333 - return None
334 - return Elasticsearch(
335 - [attributes["connector_url"]],
336 - http_auth=(attributes["connector_username"], attributes["connector_password"]),
337 - verify_certs=False,
338 - timeout=15,
339 - max_retries=10,
340 - retry_on_timeout=False,
341 - )
342 -
343 -
344 -def get_agent_data(agent_id: str) -> AgentModel:
345 - """
346 - Get agent data based on the agent id from the agents table.
347 -
348 - Args:
349 - agent_id (str): Agent id.
350 -
351 - Returns:
352 - Dict[str, Any]: Agent data.
353 - """
354 - agent_details = session.query(Agents).filter(Agents.agent_id == agent_id).first()
355 - if agent_details is not None:
356 - return agent_details
357 - else:
358 - raise HTTPException(status_code=404, detail=f"Agent with id {agent_id} not found in agents table")
359 -
360 -
361 -def get_asset_type_id(os: str) -> int:
362 - """
363 - Use AssetTypeResolver to determine the asset type ID to set within DFIR-IRIS.
364 -
365 - Parameters
366 - ----------
367 - os : str
368 - The operating system (OS) string used to resolve the asset type ID.
369 -
370 - Returns
371 - -------
372 - int
373 - The ID corresponding to the asset type.
374 - """
375 - asset_resolver = AssetTypeResolver(os)
376 - return asset_resolver.get_asset_type_id()
377 -
378 -
379 -def validate_ioc_type(ioc_value: str) -> str:
380 - """
381 - Validate IoC type using validators.
382 -
383 - Parameters
384 - ----------
385 - ioc_value : str
386 - The value to validate the IoC type.
387 -
388 - Returns
389 - -------
390 - str
391 - The type of the IoC. Returns None if validation fails.
392 - """
393 - validators = [IPv4AddressValidator, HashValidator, DomainValidator]
394 - ioc_type = None
395 -
396 - for Validator in validators:
397 - validator = Validator(ioc_value)
398 - result = validator.validate()
399 -
400 - if result["success"]:
401 - ioc_type = result["ioc_type"]
402 - break
403 -
404 - if ioc_type is None:
405 - logger.error("Failed to validate IoC value.")
406 - return ioc_type
backend/app/integrations/dnstwist/routes/analyze.py deleted
-41
@@ -1,41 +0,0 @@
1 -import regex
2 -from fastapi import APIRouter
3 -from fastapi import Depends
4 -from fastapi import HTTPException
5 -from loguru import logger
6 -
7 -from app.integrations.dnstwist.schema.analyze import DomainAnalysisResponse
8 -from app.integrations.dnstwist.schema.analyze import DomainRequestBody
9 -from app.integrations.dnstwist.services.analyze import analyze_domain
10 -
11 -dnstwist_router = APIRouter()
12 -
13 -
14 -def is_domain(domain: str) -> DomainRequestBody:
15 - """
16 - Check if the provided domain is valid.
17 -
18 - Args:
19 - domain (str): The domain to check.
20 -
21 - Returns:
22 - bool: True if the domain is valid, False otherwise.
23 - """
24 - logger.info(f"Checking if domain {domain} is valid.")
25 - pattern = regex.compile(
26 - r"^(?:[a-zA-Z0-9]+([-._]?[a-zA-Z0-9]+)*\.)+[a-zA-Z]{2,}$",
27 - )
28 - if not pattern.match(domain):
29 - raise HTTPException(status_code=400, detail="Invalid domain")
30 - return DomainRequestBody(domain=domain)
31 -
32 -
33 -@dnstwist_router.post("/analyze", response_model=DomainAnalysisResponse, status_code=200, description="Analyze domain with DNS Twist")
34 -async def analyze(body: DomainRequestBody = Depends(is_domain)):
35 - return analyze_domain(body.domain)
36 -
37 -
38 -# ! TODO: Add phishing analysis - Need more clarification on this
39 -# @dnstwist_router.post('/analyze/phishing', response_model=DomainAnalysisResponse, status_code=200, description='Analyze domain with DNS Twist')
40 -# async def analyze_phishing(body: DomainRequestBody = Depends(is_domain)):
41 -# return analyze_domain_phishing(body.domain)
backend/app/integrations/dnstwist/schema/analyze.py deleted
-23
@@ -1,23 +0,0 @@
1 -from typing import List
2 -from typing import Optional
3 -
4 -from pydantic import BaseModel
5 -from pydantic import Field
6 -
7 -
8 -class DomainData(BaseModel):
9 - dns_a: Optional[List[str]]
10 - dns_mx: Optional[List[str]]
11 - dns_ns: Optional[List[str]]
12 - domain: str
13 - fuzzer: str
14 -
15 -
16 -class DomainAnalysisResponse(BaseModel):
17 - data: List[DomainData]
18 - message: str
19 - success: bool
20 -
21 -
22 -class DomainRequestBody(BaseModel):
23 - domain: str = Field("socfortress.co", description="The domain to analyze.")
backend/app/integrations/dnstwist/services/analyze.py deleted
-42
@@ -1,42 +0,0 @@
1 -import dnstwist
2 -from loguru import logger
3 -
4 -from app.integrations.dnstwist.schema.analyze import DomainAnalysisResponse
5 -from app.integrations.dnstwist.schema.analyze import DomainRequestBody
6 -
7 -
8 -def analyze_domain(domain: DomainRequestBody) -> DomainAnalysisResponse:
9 - """
10 - Analyze the domain using dnstwist and return the results for registered domains.
11 -
12 - Args:
13 - domain (DomainRequestBody): The domain to analyze.
14 -
15 - Returns:
16 - DomainAnalysisResponse: The response from DNS Twist.
17 - """
18 - logger.info(f"Analyzing domain {domain} with DNS Twist.")
19 - logger.info("Analyzing domain for registered domains.")
20 - data = dnstwist.run(domain=domain, registered=True, format="json")
21 - return DomainAnalysisResponse(data=data, message="Domain analysis completed.", success=True)
22 -
23 -
24 -def analyze_domain_phishing(domain: DomainRequestBody) -> DomainAnalysisResponse:
25 - """
26 - Analyze the domain using dnstwist and return the results for registered domains.
27 -
28 - Args:
29 - domain (DomainRequestBody): The domain to analyze.
30 -
31 - Returns:
32 - DomainAnalysisResponse: The response from DNS Twist.
33 - """
34 - logger.info(f"Analyzing domain {domain} with DNS Twist.")
35 - logger.info("Analyzing domain for registered domains.")
36 - data = dnstwist.run(
37 - domain=domain,
38 - registered=True,
39 - format="json",
40 - lsh=True,
41 - )
42 - return DomainAnalysisResponse(data=data, message="Domain analysis completed.", success=True)
backend/app/integrations/dnstwist/utils/universal.py
backend/app/smtp/routes/configure.py deleted
-70
@@ -1,70 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import HTTPException
3 -from loguru import logger
4 -
5 -from app.auth.models.users import SMTP
6 -from app.auth.models.users import SMTPInput
7 -from app.auth.services.universal import select_all_users
8 -from app.auth.utils import AuthHandler
9 -from app.db.db_session import session
10 -from app.smtp.schema.configure import SMTPResponse
11 -
12 -smtp_router = APIRouter()
13 -auth_handler = AuthHandler()
14 -
15 -
16 -@smtp_router.post("/{user_id}/register", response_model=SMTPResponse, status_code=200, description="Register new SMTP for user")
17 -async def register(user_id: int, smtp: SMTPInput):
18 - users = select_all_users()
19 - logger.info(users)
20 - if not any(x.id == user_id for x in users):
21 - raise HTTPException(status_code=400, detail="User not found")
22 - # Check if SMTP already exists for user
23 - smtp_found = session.query(SMTP).filter(SMTP.user_id == user_id).first()
24 - if smtp_found:
25 - raise HTTPException(status_code=400, detail="SMTP already exists for user")
26 - hashed_pwd = auth_handler.get_password_hash(smtp.smtp_password)
27 - u = SMTP(email=smtp.email, smtp_password=hashed_pwd, smtp_server=smtp.smtp_server, smtp_port=smtp.smtp_port, user_id=user_id)
28 - session.add(u)
29 - session.commit()
30 - return {"message": "SMTP created successfully", "success": True}
31 -
32 -
33 -@smtp_router.get("/{user_id}", response_model=SMTP, status_code=200, description="Get SMTP for user")
34 -async def get_smtp(user_id: int):
35 - users = select_all_users()
36 - if not any(x.id == user_id for x in users):
37 - raise HTTPException(status_code=400, detail="User not found")
38 - smtp_found = session.query(SMTP).filter(SMTP.user_id == user_id).first()
39 - if not smtp_found:
40 - raise HTTPException(status_code=400, detail="SMTP not found for user")
41 - return smtp_found
42 -
43 -
44 -@smtp_router.put("/{user_id}", response_model=SMTPResponse, status_code=200, description="Update SMTP for user")
45 -async def update_smtp(user_id: int, smtp: SMTPInput):
46 - users = select_all_users()
47 - if not any(x.id == user_id for x in users):
48 - raise HTTPException(status_code=400, detail="User not found")
49 - smtp_found = session.query(SMTP).filter(SMTP.user_id == user_id).first()
50 - if not smtp_found:
51 - raise HTTPException(status_code=400, detail="SMTP not found for user")
52 - smtp_found.email = smtp.email
53 - smtp_found.smtp_server = smtp.smtp_server
54 - smtp_found.smtp_port = smtp.smtp_port
55 - smtp_found.smtp_password = auth_handler.get_password_hash(smtp.smtp_password)
56 - session.commit()
57 - return {"message": "SMTP updated successfully", "success": True}
58 -
59 -
60 -@smtp_router.delete("/{user_id}", response_model=SMTPResponse, status_code=200, description="Delete SMTP for user")
61 -async def delete_smtp(user_id: int):
62 - users = select_all_users()
63 - if not any(x.id == user_id for x in users):
64 - raise HTTPException(status_code=400, detail="User not found")
65 - smtp_found = session.query(SMTP).filter(SMTP.user_id == user_id).first()
66 - if not smtp_found:
67 - raise HTTPException(status_code=400, detail="SMTP not found for user")
68 - session.delete(smtp_found)
69 - session.commit()
70 - return {"message": "SMTP deleted successfully", "success": True}
backend/app/smtp/routes/reports.py deleted
-31
@@ -1,31 +0,0 @@
1 -from fastapi import APIRouter
2 -from fastapi import HTTPException
3 -from loguru import logger
4 -
5 -from app.auth.models.users import SMTP
6 -from app.auth.models.users import SMTPInput
7 -from app.auth.services.universal import select_all_users
8 -from app.auth.utils import AuthHandler
9 -from app.db.db_session import session
10 -from app.smtp.schema.configure import SMTPResponse
11 -
12 -smtp_reports_router = APIRouter()
13 -auth_handler = AuthHandler()
14 -
15 -
16 -# ! TODO: Add SMTP reporting all things. Example is in the services/reports.py and services/create_report.py file
17 -@smtp_reports_router.post("/{user_id}/register", response_model=SMTPResponse, status_code=200, description="Register new SMTP for user")
18 -async def register(user_id: int, smtp: SMTPInput):
19 - users = select_all_users()
20 - logger.info(users)
21 - if not any(x.id == user_id for x in users):
22 - raise HTTPException(status_code=400, detail="User not found")
23 - # Check if SMTP already exists for user
24 - smtp_found = session.query(SMTP).filter(SMTP.user_id == user_id).first()
25 - if smtp_found:
26 - raise HTTPException(status_code=400, detail="SMTP already exists for user")
27 - hashed_pwd = auth_handler.get_password_hash(smtp.smtp_password)
28 - u = SMTP(email=smtp.email, smtp_password=hashed_pwd, smtp_server=smtp.smtp_server, smtp_port=smtp.smtp_port, user_id=user_id)
29 - session.add(u)
30 - session.commit()
31 - return {"message": "SMTP created successfully", "success": True}
backend/app/smtp/schema/configure.py deleted
-6
@@ -1,6 +0,0 @@
1 -from pydantic import BaseModel
2 -
3 -
4 -class SMTPResponse(BaseModel):
5 - message: str
6 - success: bool
backend/app/smtp/services/create_report.py deleted
-143
@@ -1,143 +0,0 @@
1 -import urllib.request
2 -from typing import List
3 -
4 -import matplotlib
5 -from loguru import logger
6 -from reportlab.lib.pagesizes import letter
7 -from reportlab.lib.styles import getSampleStyleSheet
8 -from reportlab.lib.units import inch
9 -
10 -# from reportlab.pdfgen import canvas
11 -from reportlab.platypus import Image
12 -from reportlab.platypus import Paragraph
13 -from reportlab.platypus import SimpleDocTemplate
14 -from reportlab.platypus import Spacer
15 -
16 -matplotlib.use(
17 - "Agg",
18 -) # set the backend to Agg which is a non-interactive backend suitable
19 -# for scripts and web servers. This should resolve the main thread is not
20 -# in main loop issue as it bypasses the need for tkinter.
21 -import matplotlib.pyplot as plt
22 -
23 -from app.services.wazuh_indexer.alerts import AlertsService
24 -
25 -# ! TODO: Just a template
26 -
27 -
28 -def fetch_alert_data(service, fetch_func):
29 - """
30 - Fetches alert data using the provided function.
31 -
32 - Args:
33 - service: An instance of the service to use for fetching data.
34 - fetch_func (function): The function to use to fetch the data.
35 -
36 - Returns:
37 - Returns the result of the fetch function.
38 - """
39 - alerts = fetch_func()
40 - logger.info(alerts)
41 - return alerts
42 -
43 -
44 -def create_bar_chart(alerts: dict, title: str, output_filename: str) -> None:
45 - """
46 - Creates a horizontal bar chart of alerts and saves it to a file.
47 -
48 - Args:
49 - alerts (dict): A dictionary containing alert data.
50 - title (str): The title for the chart.
51 - output_filename (str): The filename to save the chart to.
52 -
53 - Returns:
54 - None
55 - """
56 - entities = [alert["hostname"] for alert in alerts["alerts_by_host"]]
57 - num_alerts = [alert["number_of_alerts"] for alert in alerts["alerts_by_host"]]
58 -
59 - plt.figure(figsize=(10, 10))
60 - plt.barh(entities, num_alerts, color="blue")
61 - plt.xlabel("Number of Alerts")
62 - plt.ylabel("Hostnames")
63 - plt.title(title)
64 - plt.tight_layout()
65 - plt.savefig(output_filename)
66 -
67 -
68 -def create_pie_chart(alerts: dict, title: str, output_filename: str) -> None:
69 - """
70 - Creates a pie chart of alerts and saves it to a file.
71 -
72 - Args:
73 - alerts (dict): A dictionary containing alert data.
74 - title (str): The title for the chart.
75 - output_filename (str): The filename to save the chart to.
76 -
77 - Returns:
78 - None
79 - """
80 - entities = [alert["rule"] for alert in alerts["alerts_by_rule"]]
81 - num_alerts = [alert["number_of_alerts"] for alert in alerts["alerts_by_rule"]]
82 -
83 - plt.figure(figsize=(10, 6))
84 - plt.pie(num_alerts, labels=entities, autopct="%1.1f%%")
85 - plt.legend(entities, loc="lower right", bbox_to_anchor=(1.0, 1.0)) # Add this line to include a legend
86 - plt.title(title)
87 - plt.tight_layout()
88 - plt.savefig(output_filename)
89 -
90 -
91 -def create_pdf(title: str, image_filenames: List[str], pdf_filename: str) -> None:
92 - """
93 - Creates a PDF containing images.
94 -
95 - Args:
96 - title (str): The title for the PDF.
97 - image_filenames (List[str]): A list of image filenames to include in the PDF.
98 - pdf_filename (str): The filename to save the PDF to.
99 -
100 - Returns:
101 - None
102 - """
103 - # Download the SOC Fortress logo
104 - logo_url = "https://socfortress-images.s3.amazonaws.com/socfortress_logo_orange.png"
105 - logo_filename = "socfortress_logo_orange.png"
106 - urllib.request.urlretrieve(logo_url, logo_filename)
107 -
108 - doc = SimpleDocTemplate(pdf_filename, pagesize=letter)
109 - styles = getSampleStyleSheet()
110 - Story = []
111 -
112 - # Add a cover page
113 - Story.append(Spacer(1, 2 * inch))
114 - Story.append(Image(logo_filename, 5 * inch, 5 * inch)) # Adjust size as needed
115 - Story.append(Spacer(1, 1 * inch))
116 - style = styles["Title"]
117 - Story.append(Paragraph(title, style))
118 - Story.append(Spacer(1, 2 * inch))
119 -
120 - # Add the images
121 - for i, image_filename in enumerate(image_filenames):
122 - Story.append(Image(image_filename, 6 * inch, 4 * inch)) # Adjust size as needed
123 - Story.append(Spacer(1, 0.2 * inch))
124 -
125 - doc.build(Story)
126 -
127 -
128 -def create_alerts_report_pdf() -> None:
129 - """
130 - Creates a PDF report of alerts including a bar chart and a pie chart.
131 -
132 - Returns:
133 - None
134 - """
135 - service = AlertsService()
136 -
137 - alerts_by_host = fetch_alert_data(service, service.collect_alerts_by_host)
138 - create_bar_chart(alerts_by_host, "Number of Alerts by Host", "alerts_by_host.png")
139 -
140 - alerts_by_rules = fetch_alert_data(service, service.collect_alerts_by_rule)
141 - create_pie_chart(alerts_by_rules, "Number of Alerts by Rule", "alerts_by_rule.png")
142 -
143 - create_pdf("Test", ["alerts_by_host.png", "alerts_by_rule.png"], "alerts_report.pdf")
backend/app/smtp/services/reports.py deleted
-116
@@ -1,116 +0,0 @@
1 -import smtplib
2 -from email import encoders
3 -from email.mime.base import MIMEBase
4 -from email.mime.multipart import MIMEMultipart
5 -from email.mime.text import MIMEText
6 -from typing import List
7 -
8 -from app.services.smtp.create_report import create_alerts_report_pdf
9 -from app.services.smtp.universal import EmailTemplate
10 -from app.services.smtp.universal import UniversalEmailCredentials
11 -
12 -# ! SEND REPORT
13 -
14 -
15 -class EmailReportSender:
16 - """
17 - Class for sending an email report with PDF attachments.
18 - """
19 -
20 - def __init__(self, to_email: str):
21 - """
22 - Constructor for the EmailReportSender class.
23 -
24 - Args:
25 - to_email (str): The email address to send the report to.
26 - """
27 - self.to_email = to_email
28 -
29 - def _get_credentials(self) -> dict:
30 - """
31 - Fetches the email credentials.
32 -
33 - Returns:
34 - dict: A dictionary containing the email credentials. If no credentials are found,
35 - the dictionary contains an "error" key.
36 - """
37 - try:
38 - return UniversalEmailCredentials.read_all()["emails_configured"][0]
39 - except IndexError:
40 - return {"error": "No email credentials found"}
41 -
42 - def create_email_message(self, subject: str, body: str) -> MIMEMultipart:
43 - """
44 - Creates an email message with the provided subject and body.
45 -
46 - Args:
47 - subject (str): The subject of the email.
48 - body (str): The body of the email.
49 -
50 - Returns:
51 - MIMEMultipart: An email message object. If an error occurs while fetching credentials,
52 - the return value is a dictionary containing an "error" key.
53 - """
54 - msg = MIMEMultipart()
55 - credentials = self._get_credentials()
56 - if "error" in credentials:
57 - return credentials
58 - msg["From"] = credentials["email"]
59 - msg["To"] = self.to_email
60 - msg["Subject"] = subject
61 - msg.attach(MIMEText(body, "html"))
62 - return msg
63 -
64 - def attach_pdfs(self, msg: MIMEMultipart, filenames: List[str]) -> MIMEMultipart:
65 - """
66 - Attaches PDF files to an email message.
67 -
68 - Args:
69 - msg (MIMEMultipart): The email message to attach the PDFs to.
70 - filenames (List[str]): A list of filenames of the PDFs to attach.
71 -
72 - Returns:
73 - MIMEMultipart: The email message with the attached PDFs.
74 - """
75 - for filename in filenames:
76 - with open(filename, "rb") as attachment_file:
77 - part = MIMEBase("application", "octet-stream")
78 - part.set_payload(attachment_file.read())
79 - encoders.encode_base64(part)
80 - part.add_header("Content-Disposition", f"attachment; filename= {filename}")
81 - msg.attach(part)
82 - return msg
83 -
84 - def send_email_with_pdf(self):
85 - """
86 - Sends an email with a PDF report.
87 -
88 - Returns:
89 - dict: A dictionary containing a "message" key describing the result of the operation
90 - and a "success" key indicating whether the operation was successful.
91 - """
92 - # Generate the PDF report
93 - create_alerts_report_pdf()
94 -
95 - # Render the email body
96 - template = EmailTemplate("email_template")
97 - body = template.render_html_body(template_name="email_template")
98 -
99 - # Create the email message and attach the PDF
100 - msg = self.create_email_message("Test Report", body)
101 - if isinstance(msg, dict) and "error" in msg:
102 - return {"message": msg["error"], "success": False}
103 - msg = self.attach_pdfs(msg, ["alerts_report.pdf"])
104 -
105 - credentials = self._get_credentials()
106 - if "error" in credentials:
107 - return {"message": credentials["error"], "success": False}
108 -
109 - # Send the email
110 - with smtplib.SMTP(credentials["smtp_server"], credentials["smtp_port"]) as server:
111 - server.starttls()
112 - server.login(credentials["email"], credentials["password"])
113 - text = msg.as_string()
114 - server.sendmail(credentials["email"], self.to_email, text)
115 -
116 - return {"message": "Report sent successfully", "success": True}
backend/copilot.py deleted
-103
@@ -1,103 +0,0 @@
1 -import uvicorn
2 -from fastapi import FastAPI
3 -from fastapi import HTTPException
4 -from fastapi import Request
5 -from fastapi.middleware.cors import CORSMiddleware
6 -from fastapi.responses import JSONResponse
7 -
8 -from app.agents.routes.agents import agents_router
9 -from app.auth.routes.auth import user_router
10 -from app.connectors.cortex.routes.analyzers import cortex_analyzer_router
11 -from app.connectors.dfir_iris.routes.alerts import dfir_iris_alerts_router
12 -from app.connectors.dfir_iris.routes.assets import assets_router
13 -from app.connectors.dfir_iris.routes.cases import cases_router
14 -from app.connectors.dfir_iris.routes.notes import notes_router
15 -from app.connectors.dfir_iris.routes.users import dfir_iris_users_router
16 -from app.connectors.graylog.routes.collector import graylog_collector_router
17 -from app.connectors.graylog.routes.events import graylog_events_router
18 -from app.connectors.graylog.routes.management import graylog_management_router
19 -from app.connectors.graylog.routes.monitoring import graylog_monitoring_router
20 -from app.connectors.graylog.routes.pipelines import graylog_pipelines_router
21 -from app.connectors.graylog.routes.streams import graylog_streams_router
22 -from app.connectors.routes import connector_router
23 -from app.connectors.shuffle.routes.workflows import shuffle_workflows_router
24 -from app.connectors.sublime.routes.alerts import sublime_alerts_router
25 -from app.connectors.velociraptor.routes.artifacts import velociraptor_artifacts_router
26 -from app.connectors.wazuh_indexer.routes.alerts import wazuh_indexer_alerts_router
27 -
28 -# from app.connectors.wazuh_indexer.routes.routes import wazuh_indexer_router
29 -from app.connectors.wazuh_indexer.routes.monitoring import wazuh_indexer_router
30 -from app.connectors.wazuh_manager.routes.rules import wazuh_manager_router
31 -from app.customers.routes.customers import customers_router
32 -from app.db.db_session import engine
33 -from app.db.db_setup import create_tables
34 -from app.healthchecks.agents.routes.agents import healtcheck_agents_router
35 -from app.integrations.alert_escalation.routes.general_alert import (
36 - integration_general_alerts_router,
37 -)
38 -from app.integrations.dnstwist.routes.analyze import dnstwist_router
39 -from app.smtp.routes.configure import smtp_router
40 -
41 -app = FastAPI(description="CoPilot API", version="0.1.0", title="CoPilot API")
42 -
43 -# Allow all origins, methods and headers
44 -app.add_middleware(
45 - CORSMiddleware,
46 - allow_origins=["*"],
47 - allow_credentials=True,
48 - allow_methods=["*"],
49 - allow_headers=["*"],
50 -)
51 -
52 -
53 -@app.exception_handler(HTTPException)
54 -async def custom_http_exception_handler(request: Request, exc: HTTPException):
55 - return JSONResponse(
56 - status_code=exc.status_code,
57 - content={
58 - "success": False,
59 - "message": exc.detail,
60 - },
61 - )
62 -
63 -
64 -app.include_router(connector_router, prefix="/connectors", tags=["connectors"])
65 -app.include_router(wazuh_indexer_router, prefix="/wazuh_indexer", tags=["wazuh-indexer"])
66 -app.include_router(user_router, prefix="/auth", tags=["auth"])
67 -app.include_router(wazuh_manager_router, prefix="/wazuh_manager", tags=["wazuh-manager"])
68 -app.include_router(agents_router, prefix="/agents", tags=["agents"])
69 -app.include_router(graylog_monitoring_router, prefix="/graylog", tags=["graylog"])
70 -app.include_router(graylog_collector_router, prefix="/graylog", tags=["graylog"])
71 -app.include_router(graylog_events_router, prefix="/graylog", tags=["graylog"])
72 -app.include_router(graylog_pipelines_router, prefix="/graylog", tags=["graylog"])
73 -app.include_router(graylog_streams_router, prefix="/graylog", tags=["graylog"])
74 -app.include_router(graylog_management_router, prefix="/graylog", tags=["graylog"])
75 -app.include_router(wazuh_indexer_alerts_router, prefix="/alerts", tags=["alerts"])
76 -app.include_router(cases_router, prefix="/cases", tags=["cases"])
77 -app.include_router(notes_router, prefix="/notes", tags=["notes"])
78 -app.include_router(assets_router, prefix="/assets", tags=["assets"])
79 -app.include_router(dfir_iris_alerts_router, prefix="/alerts", tags=["soc-alerts"])
80 -app.include_router(dfir_iris_users_router, prefix="/users", tags=["dfir_iris-users"])
81 -app.include_router(cortex_analyzer_router, prefix="/analyzers", tags=["cortex-analyzers"])
82 -app.include_router(velociraptor_artifacts_router, prefix="/artifacts", tags=["velociraptor"])
83 -app.include_router(shuffle_workflows_router, prefix="/workflows", tags=["shuffle"])
84 -app.include_router(sublime_alerts_router, prefix="/sublime", tags=["sublime"])
85 -app.include_router(customers_router, prefix="/customers", tags=["customers"])
86 -app.include_router(healtcheck_agents_router, prefix="/healthcheck", tags=["healthcheck"])
87 -app.include_router(smtp_router, prefix="/smtp", tags=["smtp"])
88 -app.include_router(dnstwist_router, prefix="/dnstwist", tags=["dnstwist"])
89 -app.include_router(integration_general_alerts_router, prefix="/alerts", tags=["alerts"])
90 -
91 -
92 -@app.on_event("startup")
93 -async def init_db():
94 - create_tables(engine)
95 -
96 -
97 -@app.get("/")
98 -def hello():
99 - return {"message": "Hello World"}
100 -
101 -
102 -if __name__ == "__main__":
103 - uvicorn.run(app, host="localhost", port=5000)
backend/file-store/api.config.yaml deleted
-72
@@ -1,72 +0,0 @@
1 -ca_certificate: |
2 - -----BEGIN CERTIFICATE-----
3 - MIIDTDCCAjSgAwIBAgIRAKim5DSDvIpnbc7BN8WxDrAwDQYJKoZIhvcNAQELBQAw
4 - GjEYMBYGA1UEChMPVmVsb2NpcmFwdG9yIENBMB4XDTIzMDYwMjEzMzU0MFoXDTMz
5 - MDUzMDEzMzU0MFowGjEYMBYGA1UEChMPVmVsb2NpcmFwdG9yIENBMIIBIjANBgkq
6 - hkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAu6aTXL1/NcNsrWbqn0SWZk4d5fbwz48+
7 - SYcbZxM8aseCv7LlZEyA+aOQXyFFzdX+B3E+7+25+bfmEU6B0/9N5S0Qk+bkhw14
8 - 0+Edk9uC9qEW2LDTNAH34T4Znb2ki+OjYgB78MWfKm7AR4KxM1wfgOq9VTQtF3Vi
9 - HwieHyYkvnmwedA6McA/SxwY05XTlCOgrtenDRyDP2fRVPPbj6vVdLHb3EpjxpKP
10 - 0rB/h1hoePaQ0l/AGZ8kWV2seCkmYkf+drbqxzHre6tbzawJjngcu2/FwW2J6yfR
11 - Xcx8ETM7o8iAuSGPWoMAljjND2+bJRz2t6GJibL749tkge2lE5NnRQIDAQABo4GM
12 - MIGJMA4GA1UdDwEB/wQEAwICpDAdBgNVHSUEFjAUBggrBgEFBQcDAQYIKwYBBQUH
13 - AwIwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUiXe4VSsY3g0DVOwxHGl7SDhV
14 - zm8wKAYDVR0RBCEwH4IdVmVsb2NpcmFwdG9yX2NhLnZlbG9jaWRleC5jb20wDQYJ
15 - KoZIhvcNAQELBQADggEBABPWrJ0TuRuJYVdvz8qEhW/ZhFC4fs0cPcPvfQBNiBW1
16 - n/6esctopeDw6wW9A+cTf2jHqnBSNosDOcATa+JDR3tbq6qHAbso6FkZlgcmmYkN
17 - qwcmeJMedym7UMQGSkN+PqfLx4nPGrMkDhsmacYM1IJ0mlGwnTmMqeA0/oRXNVEg
18 - x4kgieeYa5J6K48CSyWAgwwCJ03vWJ+n3cpD+hWuVmK1tn3To05AG6gHMUeSK17N
19 - qIz+2JyvBBBlwgTYUUmEYzgjNYKP0Crx57jvJZ8vs/vadpXdU29UOzeViyFEyRV4
20 - JIo2Kx/jaPfLCPd9oE37KekiGBBkCgtxVp6sKHUYX3o=
21 - -----END CERTIFICATE-----
22 -client_cert: |
23 - -----BEGIN CERTIFICATE-----
24 - MIIDWjCCAkKgAwIBAgIQS/Lj2MAoxGZCF1if5sfkUzANBgkqhkiG9w0BAQsFADAa
25 - MRgwFgYDVQQKEw9WZWxvY2lyYXB0b3IgQ0EwHhcNMjMwNjAyMTM0MzMyWhcNMjQw
26 - NjAxMTM0MzMyWjA1MRUwEwYDVQQKEwxWZWxvY2lyYXB0b3IxHDAaBgNVBAMME2lu
27 - Zm9Ac29jZm9ydHJlc3MuY28wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB
28 - AQC63lhkXRY+x6ceGQv008OrmZuG50xORhG6iYCABtkwd3scQmMTIGVMPL6Bergr
29 - s1F/d+jDVJnannrjwdY0yiP1yIgNFNVEr7li5IeSsJak58qMs2cGI6Yb3BajbtUf
30 - dFXCIADF3wUJNJEoXLOdpOL4sVsrAk9tX8XErd8iSjUqNIFykMD26YTxtM8cyQgB
31 - X9XYO0OIdMJW3TSQeQYDmS22C9v9k3wNVc3Sz2TOgxrcbbva6nA++OlYQnBfta3n
32 - QwZybJWvbUhkxrnd8Wlsu+Lvab7DGarYvaMb7c5D/YV3jyWv7K4wuFPYDmU+yuDS
33 - uBP+fF3YNXX7KUm9ki9VQBzZAgMBAAGjgYAwfjAOBgNVHQ8BAf8EBAMCBaAwHQYD
34 - VR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMAwGA1UdEwEB/wQCMAAwHwYDVR0j
35 - BBgwFoAUiXe4VSsY3g0DVOwxHGl7SDhVzm8wHgYDVR0RBBcwFYITaW5mb0Bzb2Nm
36 - b3J0cmVzcy5jbzANBgkqhkiG9w0BAQsFAAOCAQEAYYex+11Q1n5hFen2R+4kwHAy
37 - 7EhhLOhVuPDqVrQ+cYbUVWqJmf+x+HPpl8W/8bfoOefCy7r09Bpi8q7yBAKH6q3J
38 - NqkZJDByaE92UAa64M5W16ptomsA51hh4PWEsLb55IVFw0h7thSl/2daaJX1x24U
39 - oyac+rm/99NwLctJ1zBCnOWufPcwxmMLDKkBbO/cXDvgOLafqeYdagO9ssEWSI5T
40 - FSl10PNE3c2cslDwnUU5/HZ99xRa+wDv/l7zcoe81+vgGHzIAm14aOVSnBukB+Ht
41 - evdf3hnkPXNPUPq/yt/iV3VD7Dlb0AMU8ofQl9niuaX/ZSLoueSAXrLaOJgFqA==
42 - -----END CERTIFICATE-----
43 -client_private_key: |
44 - -----BEGIN RSA PRIVATE KEY-----
45 - MIIEpAIBAAKCAQEAut5YZF0WPsenHhkL9NPDq5mbhudMTkYRuomAgAbZMHd7HEJj
46 - EyBlTDy+gXq4K7NRf3fow1SZ2p5648HWNMoj9ciIDRTVRK+5YuSHkrCWpOfKjLNn
47 - BiOmG9wWo27VH3RVwiAAxd8FCTSRKFyznaTi+LFbKwJPbV/FxK3fIko1KjSBcpDA
48 - 9umE8bTPHMkIAV/V2DtDiHTCVt00kHkGA5kttgvb/ZN8DVXN0s9kzoMa3G272upw
49 - PvjpWEJwX7Wt50MGcmyVr21IZMa53fFpbLvi72m+wxmq2L2jG+3OQ/2Fd48lr+yu
50 - MLhT2A5lPsrg0rgT/nxd2DV1+ylJvZIvVUAc2QIDAQABAoIBAFoBajW9GE/Yvkei
51 - 7L1Zmi925xBNK9Wvri5YuEnyAn5zrhpoZ2v4+JGF2IRo5Xg3AJQS30vl3c0M9Efr
52 - Pw9iJXvmwJD8bdSNhw9430vqPkTjWA35AzBTz1gv47+ITKK/1+aOn5Cu4LAUX64/
53 - KExP9PqwAidvD26w6ILY9FaBw6W1nZy5ZfpZi4RnnKmnBxTpzO0yTBgT9nBlRhYU
54 - BmtDq5PcO0G8THKso1N/1IT2mKTA+475OjSrnJpXb43itiNazz+u5c4WMnlGRbBY
55 - qPlu+pqQ99Jf6TefsILRMEDBNsYiIydwqfp2CRgZWblMero8wVuDIG5fwzqWvtof
56 - 71lDJ3kCgYEA49E9F5lenpSx2tyTNS+Y8RP16mwUkvKPuxBIT63kzaRCh+1XLeNg
57 - TNdnkXHLEohsdLYewITsgNOrwMtMMWguSAlbyK0VgZvgESpAlrwWR6RSTSHJeLNW
58 - 2yj7ZkUgkSooxGv3KCh4tSJ4cwzT0N8YtHwB/U/G8w4A+fv355spmgMCgYEA0fxL
59 - 5xnHg9fzKdTWIsjQ+9byRmwhAQxkhPDrE2kq+A31xz3l/0+IKjhiIdxHrg2Qi+aN
60 - noSnXRl0j1LdbTNPTyt2AF6u59yR3owYS7kIc22kw/OuNdDKa1hTAPd4mucwvwlf
61 - vJp0kMqek5hPwMKHYxNunji5oSAKLeoQrJKzpPMCgYAJksdgcH97ZoA61D4TZBan
62 - OtGAsl4C9tJ3Z+3B+2q8AYUSNTUOpplrYTnm8MM52iXEmcqdCHjvyPVUurZO9TPM
63 - ryf+PNfEhIpb7kyciPbet9cFir/upIqn1wcJeyotL3pbFrZiJ6E662HoY8ea5WUi
64 - YHus62dO223LE32NbAXJhQKBgQCNyJgLm/l+SWLTvPU1mXiag0ElUb9bMN3ycaWY
65 - fqtXwD1S4bWZlT7wmw+Po2f22wvdmrfG7/T9xMFSQPZn1HxZjZPongXlYqZPqTKC
66 - dpaBMehNswzPI4J5xrKM9YvGtBHS++Zbt8K7PUSjjfVTx0WZHTbUuKKGa9bTt7a4
67 - f3mzBwKBgQCaL8PuI05KZ0WxzY2b4WFErMBO01iG4ueVhslCn1c1w5GNlRDRh/Gy
68 - ocit+c0jeOBkA+JjkyFjf7KhgzPXJQ/BmS1UeaNStUy/TPY/gx3mjuMmli+i/Z7V
69 - drZUWaGTet9/lZLvp1jYg2shnsRyaexXUV9wlrkPft5TJhHNKvAVOw==
70 - -----END RSA PRIVATE KEY-----
71 -api_connection_string: ashvlo01.socfortress.local:8001
72 -name: info@socfortress.co
backend/requirements.in deleted
-33
@@ -1,33 +0,0 @@
1 -bcrypt
2 -blueprint
3 -cortex4py
4 -dfir_iris_client
5 -dnstwist
6 -elasticsearch7==7.10.1
7 -environs
8 -fastapi
9 -libmagic
10 -loguru
11 -marshmallow-sqlalchemy
12 -matplotlib
13 -mitreattack-python
14 -openai
15 -passlib[bcrypt]
16 -pcre2
17 -pika
18 -psycopg2-binary
19 -python-multipart
20 -pytest
21 -python-magic
22 -passlib
23 -PyJWT
24 -pydantic[email]
25 -python-jose[cryptography]
26 -pyvelociraptor~=0.1
27 -regex
28 -uvicorn
29 -sqlmodel
30 -reportlab
31 -requests
32 -xmltodict
33 -werkzeug