main
py 160 lines 6.34 KB
Raw
1 from fastapi import APIRouter
2 from fastapi import Depends
3 from fastapi import File
4 from fastapi import Form
5 from fastapi import HTTPException
6 from fastapi import Response
7 from fastapi import Security
8 from fastapi import UploadFile
9 from sqlalchemy.ext.asyncio import AsyncSession
10
11 from app.active_response.schema.sysmon_config import SysmonConfigContentResponse
12 from app.active_response.schema.sysmon_config import SysmonConfigDeploymentResult
13 from app.active_response.schema.sysmon_config import SysmonConfigListResponse
14 from app.active_response.schema.sysmon_config import SysmonConfigUploadResponse
15 from app.active_response.services.sysmon_config import check_config_exists
16 from app.active_response.services.sysmon_config import deploy_sysmon_config_to_worker
17 from app.active_response.services.sysmon_config import validate_sysmon_config
18 from app.auth.routes.auth import AuthHandler
19 from app.data_store.data_store_operations import download_sysmon_config
20 from app.data_store.data_store_operations import list_sysmon_configs
21 from app.data_store.data_store_operations import upload_sysmon_config
22 from app.db.db_session import get_db
23
24 sysmon_config_router = APIRouter()
25
26
27 @sysmon_config_router.post(
28 "/upload",
29 response_model=SysmonConfigUploadResponse,
30 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
31 )
32 async def upload_customer_sysmon_config(
33 customer_code: str = Form(...),
34 file: UploadFile = File(...),
35 session: AsyncSession = Depends(get_db),
36 ):
37 """Upload a sysmon config XML file for a specific customer."""
38 # Validate file extension
39 if not file.filename.endswith(".xml"):
40 raise HTTPException(status_code=400, detail="Only XML files are accepted for sysmon configs")
41
42 try:
43 # Read and validate file content
44 file_content = await file.read()
45 xml_content = file_content.decode("utf-8")
46 await validate_sysmon_config(xml_content)
47
48 # Reset file pointer for upload
49 await file.seek(0)
50
51 # Check if file exists already
52 file_exists = await check_config_exists(customer_code)
53
54 # Upload file
55 await upload_sysmon_config(customer_code, file)
56
57 return SysmonConfigUploadResponse(
58 success=True,
59 message=f"Successfully {'updated' if file_exists else 'uploaded'} Sysmon config",
60 customer_code=customer_code,
61 filename="sysmon_config.xml",
62 overwritten=file_exists,
63 )
64 except UnicodeDecodeError:
65 raise HTTPException(status_code=400, detail="File is not valid UTF-8 encoded text")
66
67
68 # This route must come before the /{customer_code} route
69 @sysmon_config_router.get(
70 "/content/{customer_code}",
71 response_model=SysmonConfigContentResponse,
72 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
73 )
74 async def get_customer_sysmon_config_content(customer_code: str, session: AsyncSession = Depends(get_db)):
75 """Get the sysmon config for a specific customer as a string."""
76 try:
77 data_bytes = await download_sysmon_config(customer_code)
78 xml_content = data_bytes.decode("utf-8")
79
80 return SysmonConfigContentResponse(
81 success=True,
82 message="Successfully retrieved Sysmon config",
83 customer_code=customer_code,
84 config_content=xml_content,
85 )
86 except UnicodeDecodeError:
87 raise HTTPException(status_code=500, detail="Failed to decode config file as UTF-8")
88 except HTTPException:
89 raise
90 except Exception as e:
91 raise HTTPException(status_code=500, detail=f"Error retrieving config: {str(e)}")
92
93
94 @sysmon_config_router.get("", response_model=SysmonConfigListResponse, dependencies=[Security(AuthHandler().require_any_scope("admin"))])
95 async def get_all_sysmon_configs(session: AsyncSession = Depends(get_db)):
96 """List all customers that have sysmon configs."""
97 customers = await list_sysmon_configs()
98
99 return SysmonConfigListResponse(
100 success=True,
101 message="Successfully retrieved list of customer sysmon configs",
102 customer_codes=customers,
103 )
104
105
106 @sysmon_config_router.get("/{customer_code}", dependencies=[Security(AuthHandler().require_any_scope("admin"))])
107 async def get_customer_sysmon_config(customer_code: str, session: AsyncSession = Depends(get_db)):
108 """Download the sysmon config for a specific customer."""
109 try:
110 data = await download_sysmon_config(customer_code)
111 return Response(
112 content=data,
113 media_type="application/xml",
114 headers={"Content-Disposition": f"attachment; filename=sysmon_config_{customer_code}.xml"},
115 )
116 except HTTPException:
117 raise
118 except Exception as e:
119 raise HTTPException(status_code=500, detail=f"Error retrieving config: {str(e)}")
120
121
122 @sysmon_config_router.post(
123 "/deploy/{customer_code}",
124 response_model=SysmonConfigDeploymentResult,
125 dependencies=[Security(AuthHandler().require_any_scope("admin"))],
126 )
127 async def deploy_sysmon_config(customer_code: str, session: AsyncSession = Depends(get_db)):
128 """
129 Deploy a customer's Sysmon config to the Wazuh master.
130 Fetches the config from MinIO storage and sends it to the Wazuh master.
131 The Wazuh Master needs to be running the Customer-Provisioning-Worker application.
132 Currently we are invoking this via a wodle command that must be placed in the wazuh agent group.
133 <wodle name="command">
134 <disabled>no</disabled>
135 <tag>sysmon-reload</tag>
136 <command>"C:\Program Files (x86)\ossec-agent\active-response\bin\run_sysmon_config_reload.cmd"</command>
137 <interval>24h</interval>
138 <ignore_output>yes</ignore_output>
139 <run_on_start>yes</run_on_start>
140 <timeout>0</timeout>
141 </wodle>
142 Might revisit in the future to use the Wazuh API directly to invoke the active response.
143 The current limitation with the active-response is that we have to wait for the manager to pass the new sysmon_config.xml
144 file to the agent.
145 I.E:
146 {
147 "endpoint": "/active-response",
148 "arguments": [],
149 "command": "sysmon_config_reload",
150 "custom": true,
151 "alert": {
152 "action": "sysmon_config_reload"
153 },
154 "params": {
155 "wait_for_complete": true,
156 "agents_list": ["085"]
157 }
158 }
159 """
160 return await deploy_sysmon_config_to_worker(customer_code=customer_code, session=session)