main
py 313 lines 12.1 KB
Raw
1 from fastapi import HTTPException
2 from loguru import logger
3 from sqlalchemy.ext.asyncio import AsyncSession
4
5 from app.connectors.grafana.utils.universal import create_grafana_client
6 from app.connectors.wazuh_indexer.utils.universal import create_wazuh_indexer_client
7 from app.customer_provisioning.schema.grafana import GrafanaDatasource
8 from app.customer_provisioning.schema.grafana import GrafanaDataSourceCreationResponse
9 from app.customer_provisioning.schema.grafana import GrafanaFolderCreationResponse
10 from app.customer_provisioning.schema.grafana import GrafanaOrganizationCreation
11 from app.customer_provisioning.schema.grafana import NodesVersionResponse
12 from app.customer_provisioning.schema.provision import ProvisionNewCustomer
13 from app.utils import get_connector_attribute
14
15
16 ################# ! GRAFANA PROVISIONING ! #################
17 async def create_grafana_organization(
18 request: ProvisionNewCustomer,
19 ) -> GrafanaOrganizationCreation:
20 """
21 Creates a Grafana organization for a customer.
22
23 Args:
24 request (ProvisionNewCustomer): The request object containing customer information.
25
26 Returns:
27 GrafanaOrganizationCreation: The created Grafana organization.
28
29 """
30 logger.info(f"Creating Grafana organization for customer {request.customer_name}")
31 grafana_client = await create_grafana_client("Grafana")
32 results = grafana_client.organization.create_organization(
33 organization={
34 "name": request.customer_grafana_org_name,
35 },
36 )
37 return GrafanaOrganizationCreation(**results)
38
39
40 async def create_grafana_datasource(
41 request: ProvisionNewCustomer,
42 organization_id: int,
43 session: AsyncSession,
44 ) -> GrafanaDataSourceCreationResponse:
45 """
46 Creates a Grafana Wazuh datasource for a new customer using the OpenSearch Data Source.
47
48 Args:
49 request (ProvisionNewCustomer): The request object containing customer information.
50 organization_id (int): The ID of the organization to create the datasource for.
51 session (AsyncSession): The database session.
52
53 Returns:
54 GrafanaDataSourceCreationResponse: The response object containing the result of the datasource creation.
55 """
56 logger.info("Creating Grafana datasource")
57 grafana_client = await create_grafana_client("Grafana")
58 # Switch to the newly created organization
59 grafana_client.user.switch_actual_user_organisation(organization_id)
60 datasource_payload = GrafanaDatasource(
61 name="WAZUH",
62 type="grafana-opensearch-datasource",
63 typeName="OpenSearch",
64 access="proxy",
65 url=await get_connector_attribute(
66 connector_name="Wazuh-Indexer",
67 column_name="connector_url",
68 session=session,
69 ),
70 database=f"{request.customer_index_name}*",
71 basicAuth=True,
72 basicAuthUser=await get_connector_attribute(
73 connector_name="Wazuh-Indexer",
74 column_name="connector_username",
75 session=session,
76 ),
77 secureJsonData={
78 "basicAuthPassword": await get_connector_attribute(
79 connector_name="Wazuh-Indexer",
80 column_name="connector_password",
81 session=session,
82 ),
83 },
84 isDefault=False,
85 jsonData={
86 "dataLinks": [
87 {"field": "^data_vulnerability_cve$", "url": "https://nvd.nist.gov/vuln/detail/${__value.raw}"},
88 {
89 "field": "^_id$",
90 "url": (
91 "{}/explore?left=%7B%22datasource%22:%22WAZUH%22,%22queries%22:%5B%7B"
92 "%22refId%22:%22A%22,%22query%22:%22_id:${{__value.raw}}%22,%22alias%22:%22%22,"
93 "%22metrics%22:%5B%7B%22id%22:%221%22,%22type%22:%22logs%22,%22settings%22:"
94 "%7B%22limit%22:%22500%22%7D%7D%5D,%22bucketAggs%22:%5B%7B%22type%22:%22date_histogram%22,%22id%22:%221%22,%22settings%22:%7B%22interval%22:%22auto%22%7D%7D%5D,%22timeField%22:"
95 "%22timestamp%22%7D%5D,%22range%22:%7B%22from%22:%22now-6h%22,%22to%22:%22now%22%7D%7D"
96 ).format(request.grafana_url),
97 },
98 ],
99 "database": f"{request.customer_index_name}*",
100 "flavor": "opensearch",
101 "includeFrozen": False,
102 "logLevelField": "syslog_level",
103 "logMessageField": "rule_description",
104 "maxConcurrentShardRequests": 5,
105 "pplEnabled": True,
106 "timeField": "timestamp",
107 "tlsSkipVerify": True,
108 "version": await get_opensearch_version(),
109 },
110 readOnly=True,
111 )
112 results = grafana_client.datasource.create_datasource(
113 datasource=datasource_payload.model_dump(),
114 )
115 return GrafanaDataSourceCreationResponse(**results)
116
117
118 async def create_vulnerability_datasource(
119 request: ProvisionNewCustomer,
120 organization_id: int,
121 session: AsyncSession,
122 ) -> GrafanaDataSourceCreationResponse:
123 """
124 Creates a Grafana Wazuh Vulnerabilites datasource for a new customer using the OpenSearch Data Source.
125 USED WHEN WAZUH VERSION IS 4.8.0 and above
126
127 Args:
128 request (ProvisionNewCustomer): The request object containing customer information.
129 organization_id (int): The ID of the organization to create the datasource for.
130 session (AsyncSession): The database session.
131
132 Returns:
133 GrafanaDataSourceCreationResponse: The response object containing the result of the datasource creation.
134 """
135 logger.info("Creating Grafana datasource")
136 grafana_client = await create_grafana_client("Grafana")
137 # Switch to the newly created organization
138 grafana_client.user.switch_actual_user_organisation(organization_id)
139 datasource_payload = GrafanaDatasource(
140 name="VULNERABILITIES",
141 type="grafana-opensearch-datasource",
142 typeName="OpenSearch",
143 access="proxy",
144 url=await get_connector_attribute(
145 connector_id=1,
146 column_name="connector_url",
147 session=session,
148 ),
149 database=f"{request.customer_index_name}*",
150 basicAuth=True,
151 basicAuthUser=await get_connector_attribute(
152 connector_id=1,
153 column_name="connector_username",
154 session=session,
155 ),
156 secureJsonData={
157 "basicAuthPassword": await get_connector_attribute(
158 connector_id=1,
159 column_name="connector_password",
160 session=session,
161 ),
162 },
163 isDefault=False,
164 jsonData={
165 "dataLinks": [
166 {"field": "^vulnerability.id$", "url": "https://nvd.nist.gov/vuln/detail/${__value.raw}"},
167 {
168 "field": "^_id$",
169 "url": (
170 "{}/explore?left=%7B%22datasource%22:%22WAZUH%22,%22queries%22:%5B%7B"
171 "%22refId%22:%22A%22,%22query%22:%22_id:${{__value.raw}}%22,%22alias%22:%22%22,"
172 "%22metrics%22:%5B%7B%22id%22:%221%22,%22type%22:%22logs%22,%22settings%22:"
173 "%7B%22limit%22:%22500%22%7D%7D%5D,%22bucketAggs%22:%5B%7B%22type%22:%22date_histogram%22,%22id%22:%221%22,%22settings%22:%7B%22interval%22:%22auto%22%7D%7D%5D,%22timeField%22:"
174 "%22timestamp%22%7D%5D,%22range%22:%7B%22from%22:%22now-6h%22,%22to%22:%22now%22%7D%7D"
175 ).format(request.grafana_url),
176 },
177 ],
178 "database": "wazuh-states-vulnerabilities*",
179 "flavor": "opensearch",
180 "includeFrozen": False,
181 "logLevelField": "vulnerability.score.base",
182 "logMessageField": "vulnerability.id",
183 "maxConcurrentShardRequests": 5,
184 "pplEnabled": True,
185 "timeField": "timestamp",
186 "tlsSkipVerify": True,
187 "version": await get_opensearch_version(),
188 },
189 readOnly=True,
190 )
191 results = grafana_client.datasource.create_datasource(
192 datasource=datasource_payload.model_dump(),
193 )
194 return GrafanaDataSourceCreationResponse(**results)
195
196
197 async def create_grafana_folder(
198 organization_id: int,
199 folder_title: str,
200 ) -> GrafanaFolderCreationResponse:
201 """
202 Creates a Grafana folder in the specified organization.
203
204 Args:
205 organization_id (int): The ID of the organization where the folder will be created.
206 folder_title (str): The title of the folder.
207
208 Returns:
209 GrafanaFolderCreationResponse: The response object containing the details of the created folder.
210 """
211 logger.info("Creating Grafana folder")
212 grafana_client = await create_grafana_client("Grafana")
213 # Switch to the newly created organization
214 grafana_client.user.switch_actual_user_organisation(organization_id)
215 results = grafana_client.folder.create_folder(
216 title=folder_title,
217 )
218 logger.info(f"Folder creation results: {results}")
219 return GrafanaFolderCreationResponse(**results)
220
221
222 async def get_opensearch_version() -> str:
223 """
224 Retrieves the version of OpenSearch.
225
226 Returns:
227 str: The version of OpenSearch.
228
229 Raises:
230 HTTPException: If the OpenSearch version cannot be retrieved.
231 """
232 logger.info("Getting OpenSearch version")
233 opensearch_client = await create_wazuh_indexer_client("Wazuh-Indexer")
234
235 # Retrieve version information
236 version_response = opensearch_client.nodes.info(
237 node_id="_local",
238 filter_path=["nodes.*.version"],
239 )
240
241 # Parse the response to get the first version found
242 nodes_version_response = NodesVersionResponse(**version_response)
243 for node_id, node_info in nodes_version_response.nodes.items():
244 return node_info.version
245
246 # If no version is found, raise an exception
247 raise HTTPException(
248 status_code=500,
249 detail="Failed to retrieve OpenSearch version.",
250 )
251
252
253 ################# ! GRAFANA DECOMISSIONING ! #################
254 async def delete_grafana_organization(organization_id: int):
255 """
256 Deletes a Grafana organization.
257
258 Args:
259 organization_id (int): The ID of the organization to delete.
260 """
261 logger.info("Deleting Grafana organization")
262 grafana_client = await create_grafana_client("Grafana")
263 try:
264 organization_deleted = grafana_client.organizations.delete_organization(
265 organization_id=organization_id,
266 )
267 logger.info(f"Organization deleted: {organization_deleted}")
268 except Exception as e:
269 # Switch the organization to the default and try again
270 logger.info(
271 f"Failed to delete organization: {e}. Switching to default organization and trying again.",
272 )
273 grafana_client.user.switch_actual_user_organisation(1)
274 organization_deleted = grafana_client.organizations.delete_organization(
275 organization_id=organization_id,
276 )
277 logger.info(f"Organization deleted: {organization_deleted}")
278 return organization_deleted
279 return organization_deleted
280
281
282 async def delete_grafana_dashboard_folder(organization_id: int, folder_uid: str):
283 """
284 Deletes a Grafana dashboard folder.
285
286 Args:
287 folder_uid (str): The ID of the folder to delete.
288 """
289 logger.info("Deleting Grafana folder")
290 grafana_client = await create_grafana_client("Grafana")
291 grafana_client.user.switch_actual_user_organisation(organization_id)
292 folder_deleted = grafana_client.folder.delete_folder(
293 uid=folder_uid,
294 )
295 logger.info(f"Folder deleted: {folder_deleted}")
296 return folder_deleted
297
298
299 async def delete_grafana_datasource(organization_id: int, datasource_uid: str):
300 """
301 Deletes a Grafana datasource.
302
303 Args:
304 datasource_uid (int): The ID of the datasource to delete.
305 """
306 logger.info("Deleting Grafana datasource")
307 grafana_client = await create_grafana_client("Grafana")
308 grafana_client.user.switch_actual_user_organisation(organization_id)
309 datasource_deleted = grafana_client.datasource.delete_datasource_by_uid(
310 datasource_uid=datasource_uid,
311 )
312 logger.info(f"Datasource deleted: {datasource_deleted}")
313 return datasource_deleted