main
py 178 lines 7.09 KB
Raw
1 import json
2 from pathlib import Path
3
4 import aiofiles
5 from fastapi import APIRouter
6 from fastapi import Depends
7 from fastapi import HTTPException
8 from fastapi import Security
9 from loguru import logger
10 from sqlalchemy.ext.asyncio import AsyncSession
11
12 from app.active_response.schema.active_response import ActiveResponse
13 from app.active_response.schema.active_response import ActiveResponseDetails
14 from app.active_response.schema.active_response import ActiveResponseDetailsResponse
15 from app.active_response.schema.active_response import ActiveResponsesSupported
16 from app.active_response.schema.active_response import ActiveResponsesSupportedResponse
17 from app.active_response.schema.active_response import InvokeActiveResponseRequest
18 from app.active_response.schema.active_response import InvokeActiveResponseResponse
19 from app.agents.routes.agents import get_agent
20 from app.auth.models.users import User
21 from app.auth.utils import AuthHandler
22 from app.connectors.wazuh_manager.utils.universal import send_put_request
23 from app.db.db_session import get_db
24
25 active_response_router = APIRouter()
26
27
28 async def verify_active_response_name(active_response_name: str) -> None:
29 """
30 Verify the active response name
31 """
32 active_response_name = active_response_name.upper()
33 if active_response_name not in ActiveResponsesSupported.__members__:
34 raise HTTPException(status_code=404, detail="Active Response not found")
35
36
37 def get_markdown_content_path(directory: str, filename: str) -> str:
38 """
39 Get the path to the markdown content
40 """
41 current_directory = Path(__file__).parent.parent
42 return str(current_directory / f"scripts/{directory}/{filename}")
43
44
45 async def read_markdown_file(file_path: str) -> str:
46 """
47 Read the content of a markdown file
48 """
49 async with aiofiles.open(file_path, "r") as file:
50 return await file.read()
51
52
53 async def return_supported_active_responses_based_on_os(os: str) -> ActiveResponsesSupportedResponse:
54 # if os contains windows
55 if "Windows" in os:
56 logger.info("Agent OS is Windows")
57 return ActiveResponsesSupportedResponse(
58 supported_active_responses=[
59 ActiveResponse(name=active_response.name, description=active_response.value)
60 for active_response in ActiveResponsesSupported
61 if "WINDOWS" in active_response.name
62 ],
63 success=True,
64 message="Supported Active Responses retrieved successfully",
65 )
66 else:
67 logger.info("Agent OS is Linux")
68 return ActiveResponsesSupportedResponse(
69 supported_active_responses=[
70 ActiveResponse(name=active_response.name, description=active_response.value)
71 for active_response in ActiveResponsesSupported
72 if "LINUX" in active_response.name
73 ],
74 success=True,
75 message="Supported Active Responses retrieved successfully",
76 )
77
78
79 @active_response_router.get(
80 "/describe/{active_response_name}",
81 response_model=ActiveResponseDetailsResponse,
82 description="Get the details of a specific active response",
83 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
84 )
85 async def get_active_response_details_route(active_response_name: str) -> ActiveResponseDetailsResponse:
86 """
87 Get the details of a specific active response
88 """
89 await verify_active_response_name(active_response_name)
90 directory = active_response_name.split("_")[0].lower()
91 file_path = get_markdown_content_path(directory, f"{active_response_name}.md")
92 logger.info(f"Reading markdown file: {file_path}")
93 response = ActiveResponseDetails(
94 name=active_response_name,
95 description=ActiveResponsesSupported[active_response_name.upper()].value,
96 markdown_content=await read_markdown_file(file_path),
97 )
98 return ActiveResponseDetailsResponse(active_response=response, success=True, message="Active Response details retrieved successfully")
99
100
101 @active_response_router.get(
102 "/supported",
103 response_model=ActiveResponsesSupportedResponse,
104 description="Get the list of supported active responses",
105 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
106 )
107 async def get_supported_active_responses_route() -> ActiveResponsesSupportedResponse:
108 """
109 Get the list of supported active responses
110 """
111 return ActiveResponsesSupportedResponse(
112 supported_active_responses=[
113 ActiveResponse(name=active_response.name, description=active_response.value) for active_response in ActiveResponsesSupported
114 ],
115 success=True,
116 message="Supported Active Responses retrieved successfully",
117 )
118
119
120 @active_response_router.get(
121 "/supported/{agent_id}",
122 response_model=ActiveResponsesSupportedResponse,
123 description="Get the list of supported active responses for a specific agent",
124 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
125 )
126 async def get_supported_active_responses_agent_route(
127 agent_id: str,
128 current_user: User = Depends(AuthHandler().get_current_user),
129 db: AsyncSession = Depends(get_db),
130 ) -> ActiveResponsesSupportedResponse:
131 """
132 Get the list of supported active responses for a specific agent
133 """
134 response = await get_agent(agent_id, current_user, db)
135 agent = response.agents[0] if response.agents else None
136 logger.info(f"Agent: {agent.os if agent else 'None'}")
137 if agent and agent.os:
138 return await return_supported_active_responses_based_on_os(agent.os)
139 raise HTTPException(status_code=404, detail="Agent not found")
140
141
142 @active_response_router.post(
143 "/invoke",
144 response_model=InvokeActiveResponseResponse,
145 description="Invoke an active response",
146 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
147 )
148 async def invoke_active_response_route(
149 request: InvokeActiveResponseRequest,
150 ) -> InvokeActiveResponseResponse:
151 """
152 Invoke an active response.
153
154 Args:
155 request (InvokeActiveResponseRequest): The request object containing the command, custom, arguments, and alert.
156
157 Returns:
158 InvokeActiveResponseResponse: The response object indicating the success or failure of the active response invocation.
159 """
160 logger.info("Invoking Wazuh Active Response...")
161 # Append '0' to the command - This is required for Wazuh Active Response
162 request.command = f"{request.command.value}0"
163 # Create a dictionary with the request data
164 data_dict = {"command": request.command, "arguments": request.arguments, "alert": request.alert}
165 response = await send_put_request(
166 endpoint=request.endpoint,
167 data=json.dumps(data_dict),
168 params=request.params,
169 debug=True,
170 )
171 if not response["success"]:
172 logger.error(f"Request failed: {response.get('message')}")
173 if "raw_response" in response:
174 logger.error(f"Raw response: {response['raw_response']}")
175 if "error_detail" in response:
176 logger.error(f"Error details: {response['error_detail']}")
177
178 return InvokeActiveResponseResponse(success=True, message="Wazuh Active Response invoked successfully")