main
py 305 lines 11.7 KB
Raw
1 import requests
2 from fastapi import HTTPException
3 from loguru import logger
4 from sqlalchemy import update
5 from sqlalchemy.ext.asyncio import AsyncSession
6 from sqlalchemy.future import select
7
8 from app.connectors.portainer.services.stack import delete_wazuh_customer_stack
9 from app.connectors.utils import is_connector_verified
10 from app.customer_provisioning.schema.decommission import DecommissionCustomerResponse
11 from app.customer_provisioning.schema.wazuh_worker import DecommissionWorkerRequest
12 from app.customer_provisioning.schema.wazuh_worker import DecommissionWorkerResponse
13 from app.customer_provisioning.services.grafana import delete_grafana_organization
14 from app.customer_provisioning.services.graylog import delete_index_set
15 from app.customer_provisioning.services.graylog import delete_stream
16 from app.customer_provisioning.services.portainer import list_node_ips
17 from app.customer_provisioning.services.wazuh_manager import delete_wazuh_agents
18 from app.customer_provisioning.services.wazuh_manager import delete_wazuh_groups
19 from app.customer_provisioning.services.wazuh_manager import gather_wazuh_agents
20 from app.db.universal_models import CustomersMeta
21 from app.utils import get_connector_attribute
22
23
24 async def get_customer_portainer_stack_id(
25 customer_name: str,
26 session: AsyncSession,
27 ) -> int:
28 """
29 Get the customer's Portainer stack ID from the CustomersMeta table.
30
31 Args:
32 customer_name (str): The name of the customer
33 session (AsyncSession): The database session
34
35 Returns:
36 int: The Portainer stack ID for the customer
37
38 Raises:
39 HTTPException: If the customer is not found or has no stack ID
40 """
41 logger.info(f"Getting Portainer stack ID for customer {customer_name}")
42
43 # Find the customer record
44 stmt = select(CustomersMeta).where(CustomersMeta.customer_name == customer_name)
45 result = await session.execute(stmt)
46 customer = result.scalar_one_or_none()
47
48 if not customer:
49 logger.error(f"Customer {customer_name} not found in database")
50 raise HTTPException(status_code=404, detail=f"Customer {customer_name} not found in database")
51
52 if not customer.customer_meta_portainer_stack_id:
53 logger.error(f"No Portainer stack ID found for customer {customer_name}")
54 raise HTTPException(status_code=404, detail=f"No Portainer stack ID found for customer {customer_name}")
55
56 logger.info(f"Found Portainer stack ID {customer.customer_meta_portainer_stack_id} for customer {customer_name}")
57 return customer.customer_meta_portainer_stack_id
58
59
60 async def clear_customer_portainer_stack_id(
61 customer_name: str,
62 session: AsyncSession,
63 ) -> None:
64 """
65 Clear the customer's Portainer stack ID in the CustomersMeta table by setting it to None.
66
67 Args:
68 customer_name (str): The name of the customer
69 session (AsyncSession): The database session
70
71 Raises:
72 HTTPException: If the customer is not found in the database
73 """
74 logger.info(f"Clearing Portainer stack ID for customer {customer_name}")
75
76 # Find the customer record
77 stmt = select(CustomersMeta).where(CustomersMeta.customer_name == customer_name)
78 result = await session.execute(stmt)
79 customer = result.scalar_one_or_none()
80
81 if not customer:
82 logger.error(f"Customer {customer_name} not found in database")
83 raise HTTPException(status_code=404, detail=f"Customer {customer_name} not found in database")
84
85 # Update the customer's Portainer stack ID to None
86 stmt = update(CustomersMeta).where(CustomersMeta.customer_name == customer_name).values(customer_meta_portainer_stack_id=None)
87 await session.execute(stmt)
88 await session.commit()
89
90 logger.info(f"Successfully cleared Portainer stack ID for customer {customer_name}")
91
92
93 async def decomission_wazuh_customer(
94 customer_meta: CustomersMeta,
95 session: AsyncSession,
96 ) -> DecommissionCustomerResponse:
97 """
98 Decommissions a Wazuh customer by performing the following steps:
99 1. Deletes the Wazuh Agents associated with the customer.
100 2. Deletes the Wazuh Group associated with the customer.
101 3. Deletes the Graylog Stream associated with the customer.
102 4. Deletes the Graylog Index Set associated with the customer.
103 5. Deletes the Grafana Organization associated with the customer.
104 6. Decommissions the Wazuh Worker associated with the customer.
105 7. Deletes the Customer Meta from the session.
106
107 Args:
108 customer_meta (CustomersMeta): The metadata of the customer to be decommissioned.
109 session (AsyncSession): The database session.
110
111 Returns:
112 DecommissionCustomerResponse: The response indicating the success of the decommissioning process and the deleted data.
113
114 """
115 logger.info(f"Decomissioning customer {customer_meta.customer_name}")
116
117 # Delete the Wazuh Agents
118 agents = await gather_wazuh_agents(customer_meta.customer_code)
119 agents_deleted = await delete_wazuh_agents(agents)
120 logger.info(
121 f"Deleted {agents_deleted} agents for customer {customer_meta.customer_name}",
122 )
123
124 # Delete Wazuh Group
125 groups_deleted = await delete_wazuh_groups(customer_meta.customer_code)
126
127 # Delete Graylog Stream
128 await delete_stream(customer_meta.customer_meta_graylog_stream)
129
130 # Delete Graylog Index Set
131 await delete_index_set(customer_meta.customer_meta_graylog_index)
132
133 # Delete Grafana Organization
134 await delete_grafana_organization(
135 organization_id=int(customer_meta.customer_meta_grafana_org_id),
136 )
137
138 # Decommission Wazuh Worker
139 await decommission_wazuh_worker(
140 request=DecommissionWorkerRequest(
141 customer_name=customer_meta.customer_name,
142 customer_code=customer_meta.customer_code,
143 ),
144 session=session,
145 )
146
147 # Decommission HAProxy
148 await decommission_haproxy(
149 request=DecommissionWorkerRequest(
150 customer_name=customer_meta.customer_name,
151 customer_code=customer_meta.customer_code,
152 ),
153 session=session,
154 )
155
156 # Delete Customer Meta
157 await session.delete(customer_meta)
158 await session.commit()
159
160 return DecommissionCustomerResponse(
161 message=f"Customer {customer_meta.customer_name} decomissioned successfully.",
162 success=True,
163 decomissioned_data={
164 "agents_deleted": agents_deleted,
165 "groups_deleted": groups_deleted,
166 "stream_deleted": customer_meta.customer_meta_graylog_stream,
167 "index_deleted": customer_meta.customer_meta_graylog_index,
168 },
169 )
170
171
172 ######### ! Decommission Wazuh Worker ! ############
173 async def decommission_wazuh_worker(
174 request: DecommissionWorkerRequest,
175 session: AsyncSession,
176 ) -> DecommissionWorkerResponse:
177 """
178 Decomissions a Wazuh worker. https://github.com/socfortress/Customer-Provisioning-Worker
179
180 Args:
181 request (DecommissionWorkerRequest): The request object containing the necessary information for provisioning.
182 session (AsyncSession): The async session object for making HTTP requests.
183
184 Returns:
185 ProvisionWorkerResponse: The response object indicating the success or failure of the provisioning operation.
186 """
187 logger.info(f"Decommissioning Wazuh worker {request}")
188 if await is_connector_verified(connector_name="Portainer", db=session) is False:
189 # Check if the connector is verified
190 if (
191 await get_connector_attribute(
192 connector_name="Wazuh Worker Provisioning",
193 column_name="connector_verified",
194 session=session,
195 )
196 is False
197 ):
198 logger.info("Wazuh Worker Provisioning connector is not verified, skipping ...")
199 return DecommissionWorkerResponse(
200 success=False,
201 message="Wazuh Worker Provisioning connector is not verified",
202 )
203 api_endpoint = await get_connector_attribute(
204 connector_name="Wazuh Worker Provisioning",
205 column_name="connector_url",
206 session=session,
207 )
208 # Send the POST request to the Wazuh worker
209 request.portainer_deployment = False
210 response = requests.post(
211 url=f"{api_endpoint}/provision_worker/decommission",
212 json=request.model_dump(),
213 )
214 # Check the response status code
215 if response.status_code != 200:
216 return DecommissionWorkerResponse(
217 success=False,
218 message=f"Failed to provision Wazuh worker: {response.text}",
219 )
220 # Return the response
221 return DecommissionWorkerResponse(
222 success=True,
223 message="Wazuh worker provisioned successfully",
224 )
225 else:
226 request.portainer_deployment = True
227 # ! Delete the stack via Portainer first then clean up the file system on the worker node ! #
228 # Delete the stack and get the response
229 await delete_wazuh_customer_stack(stack_id=await get_customer_portainer_stack_id(request.customer_name, session))
230
231 # Clear the stack ID from the database
232 await clear_customer_portainer_stack_id(request.customer_name, session)
233
234 swarm_node_ips = await list_node_ips()
235 logger.info(f"Invoking the customer provisioning application on the swarm node IPs: {swarm_node_ips}")
236 for ip in swarm_node_ips:
237 logger.info(f"Provisioning Wazuh worker on IP: {ip}")
238 response = requests.post(
239 url=f"http://{ip}:5003/provision_worker/decommission",
240 json=request.model_dump(),
241 )
242 logger.info(f"Status code from Wazuh Worker: {response.status_code}")
243 if response.status_code != 200:
244 return DecommissionWorkerResponse(
245 success=False,
246 message=f"Failed to provision Wazuh worker: {response.text}",
247 )
248
249 return DecommissionWorkerResponse(
250 success=True,
251 message="Wazuh worker decommissioned successfully",
252 )
253
254
255 ######### ! Decommission HAProxy ! ############
256 async def decommission_haproxy(
257 request: DecommissionWorkerRequest,
258 session: AsyncSession,
259 ) -> DecommissionWorkerResponse:
260 """
261 Decomissions a HAProxy worker.
262
263 Args:
264 request (DecommissionWorkerRequest): The request object containing the necessary information for provisioning.
265 session (AsyncSession): The async session object for making HTTP requests.
266
267 Returns:
268 ProvisionWorkerResponse: The response object indicating the success or failure of the provisioning operation.
269 """
270 logger.info(f"Decommissioning HAProxy worker {request}")
271 # Check if the connector is verified
272 if (
273 await get_connector_attribute(
274 connector_name="HAProxy Provisioning",
275 column_name="connector_verified",
276 session=session,
277 )
278 is False
279 ):
280 logger.info("HAProxy Provisioning connector is not verified, skipping ...")
281 return DecommissionWorkerResponse(
282 success=False,
283 message="HAProxy Provisioning connector is not verified",
284 )
285 api_endpoint = await get_connector_attribute(
286 connector_name="HAProxy Provisioning",
287 column_name="connector_url",
288 session=session,
289 )
290 # Send the POST request to the HAProxy worker
291 response = requests.post(
292 url=f"{api_endpoint}/provision_worker/haproxy/decommission",
293 json=request.model_dump(),
294 )
295 # Check the response status code
296 if response.status_code != 200:
297 return DecommissionWorkerResponse(
298 success=False,
299 message=f"Failed to provision HAProxy worker: {response.text}",
300 )
301 # Return the response
302 return DecommissionWorkerResponse(
303 success=True,
304 message="HAProxy worker provisioned successfully",
305 )