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