main
py 409 lines 15.7 KB
Raw
1 import json
2 from datetime import datetime
3
4 from fastapi import HTTPException
5 from loguru import logger
6 from sqlalchemy.ext.asyncio import AsyncSession
7
8 from app.connectors.grafana.schema.dashboards import DashboardProvisionRequest
9 from app.connectors.grafana.schema.dashboards import SonicwallDashboard
10 from app.connectors.grafana.services.dashboards import provision_dashboards
11 from app.connectors.grafana.utils.universal import create_grafana_client
12 from app.connectors.graylog.services.collector import (
13 get_content_pack_id_by_content_pack_name,
14 )
15 from app.connectors.graylog.services.collector import get_input_id_by_input_name
16 from app.connectors.graylog.services.collector import get_stream_id_by_stream_name
17 from app.connectors.graylog.services.streams import assign_stream_to_index
18 from app.connectors.graylog.utils.universal import send_post_request
19 from app.connectors.wazuh_indexer.services.monitoring import (
20 output_shard_number_to_be_set_based_on_nodes,
21 )
22 from app.customer_provisioning.schema.grafana import GrafanaDatasource
23 from app.customer_provisioning.schema.grafana import GrafanaDataSourceCreationResponse
24 from app.customer_provisioning.schema.graylog import GraylogIndexSetCreationResponse
25 from app.customer_provisioning.schema.graylog import StreamConnectionToPipelineRequest
26 from app.customer_provisioning.schema.graylog import TimeBasedIndexSet
27 from app.customer_provisioning.services.grafana import create_grafana_folder
28 from app.customer_provisioning.services.grafana import get_opensearch_version
29 from app.customer_provisioning.services.graylog import connect_stream_to_pipeline
30 from app.customer_provisioning.services.graylog import get_pipeline_id
31 from app.customers.routes.customers import get_customer_meta
32 from app.network_connectors.models.network_connectors import (
33 CustomerNetworkConnectorsMeta,
34 )
35 from app.stack_provisioning.graylog.schema.provision import ContentPackKeywords
36 from app.stack_provisioning.graylog.schema.provision import (
37 ProvisionNetworkContentPackRequest,
38 )
39 from app.stack_provisioning.graylog.schema.sonicwall import ProvisionSonicwallKeys
40 from app.stack_provisioning.graylog.schema.sonicwall import ProvisionSonicwallResponse
41 from app.stack_provisioning.graylog.schema.sonicwall import SonicwallCustomerDetails
42 from app.stack_provisioning.graylog.services.provision import (
43 provision_content_pack_network_connector,
44 )
45 from app.stack_provisioning.graylog.services.utils import set_deployed_flag
46 from app.utils import get_connector_attribute
47 from app.utils import get_customer_meta_attribute
48
49
50 #### ! GRAYLOG ! ####
51 async def build_index_set_config(request: SonicwallCustomerDetails) -> TimeBasedIndexSet:
52 """
53 Build the configuration for a time-based index set.
54
55 Args:
56 request (SonicwallCustomerDetails): The request object containing customer information.
57
58 Returns:
59 TimeBasedIndexSet: The configured time-based index set.
60 """
61 return TimeBasedIndexSet(
62 title=f"{request.customer_name} - SONICWALL EVENTS",
63 description=f"{request.customer_name} - SONICWALL EVENTS",
64 index_prefix=f"sonicwall-{request.customer_code}",
65 rotation_strategy_class="org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategy",
66 rotation_strategy={
67 "type": "org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategyConfig",
68 "rotation_period": "P1D",
69 "rotate_empty_index_set": False,
70 "max_rotation_period": None,
71 },
72 retention_strategy_class="org.graylog2.indexer.retention.strategies.DeletionRetentionStrategy",
73 retention_strategy={
74 "type": "org.graylog2.indexer.retention.strategies.DeletionRetentionStrategyConfig",
75 "max_number_of_indices": request.hot_data_retention,
76 },
77 creation_date=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
78 index_analyzer="standard",
79 shards=await output_shard_number_to_be_set_based_on_nodes(),
80 replicas=request.index_replicas,
81 index_optimization_max_num_segments=1,
82 index_optimization_disabled=False,
83 writable=True,
84 field_type_refresh_interval=5000,
85 )
86
87
88 # Function to send the POST request and handle the response
89 async def send_index_set_creation_request(
90 index_set: TimeBasedIndexSet,
91 ) -> GraylogIndexSetCreationResponse:
92 """
93 Sends a request to create an index set in Graylog.
94
95 Args:
96 index_set (TimeBasedIndexSet): The index set to be created.
97
98 Returns:
99 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
100 """
101 json_index_set = json.dumps(index_set.model_dump())
102 logger.info(f"json_index_set set: {json_index_set}")
103 response_json = await send_post_request(
104 endpoint="/api/system/indices/index_sets",
105 data=index_set.model_dump(),
106 )
107 return GraylogIndexSetCreationResponse(**response_json)
108
109
110 # Refactored create_index_set function
111 async def create_index_set(
112 request: SonicwallCustomerDetails,
113 ) -> GraylogIndexSetCreationResponse:
114 """
115 Creates an index set for a new customer.
116
117 Args:
118 request (SonicwallCustomerDetails): The request object containing the customer information.
119
120 Returns:
121 GraylogIndexSetCreationResponse: The response object containing the result of the index set creation.
122 """
123 logger.info(f"Creating index set for customer {request.customer_name}")
124 index_set_config = await build_index_set_config(request)
125 return await send_index_set_creation_request(index_set_config)
126
127
128 async def provision_content_pack(customer_details: SonicwallCustomerDetails):
129 """
130 Provisions a content pack for a customer.
131
132 Args:
133 customer_details (SonicwallCustomerDetails): The details of the customer.
134
135 Returns:
136 ContentPack: The provisioned content pack.
137 """
138 return await provision_content_pack_network_connector(
139 content_pack_request=ProvisionNetworkContentPackRequest(
140 content_pack_name="SONICWALL",
141 keywords=ContentPackKeywords(
142 customer_name=customer_details.customer_name,
143 customer_code=customer_details.customer_code,
144 syslog_port=customer_details.syslog_port,
145 tls_cert_file=customer_details.tls_cert_file,
146 tls_key_file=customer_details.tls_key_file,
147 ),
148 ),
149 )
150
151
152 async def get_stream_and_index_ids(customer_details: SonicwallCustomerDetails):
153 """
154 Retrieves the stream ID and index ID for a given customer.
155
156 Args:
157 customer_details (SonicwallCustomerDetails): The details of the customer.
158
159 Returns:
160 tuple: A tuple containing the stream ID and index ID.
161 """
162 stream_id = await get_stream_id_by_stream_name(stream_name=f"{customer_details.customer_name} - SONICWALL LOGS AND EVENTS")
163 index_id = (await create_index_set(request=customer_details)).data.id
164 content_pack_stream_id = await get_content_pack_id_by_content_pack_name(
165 content_pack_name=f"{customer_details.customer_name}_SONICWALL_STREAM",
166 )
167
168 content_pack_input_id = await get_content_pack_id_by_content_pack_name(
169 content_pack_name=f"{customer_details.customer_name}_SONICWALL_INPUT_SYSLOG_TLS",
170 )
171
172 return stream_id, index_id, content_pack_stream_id, content_pack_input_id
173
174
175 #### ! GRAFANA ! ####
176 async def create_grafana_datasource(
177 customer_code: str,
178 session: AsyncSession,
179 ) -> GrafanaDataSourceCreationResponse:
180 """
181 Creates a Grafana datasource for the specified customer.
182
183 Args:
184 customer_code (str): The customer code.
185 session (AsyncSession): The async session.
186
187 Returns:
188 GrafanaDataSourceCreationResponse: The response containing the created datasource details.
189 """
190 logger.info("Creating Grafana datasource")
191 grafana_client = await create_grafana_client("Grafana")
192 # Switch to the newly created organization
193 grafana_client.user.switch_actual_user_organisation(
194 (await get_customer_meta(customer_code, session)).customer_meta.customer_meta_grafana_org_id,
195 )
196 datasource_payload = GrafanaDatasource(
197 name="SONICWALL",
198 type="grafana-opensearch-datasource",
199 typeName="OpenSearch",
200 access="proxy",
201 url=await get_connector_attribute(
202 connector_id=1,
203 column_name="connector_url",
204 session=session,
205 ),
206 database=f"sonicwall-{customer_code}*",
207 basicAuth=True,
208 basicAuthUser=await get_connector_attribute(
209 connector_id=1,
210 column_name="connector_username",
211 session=session,
212 ),
213 secureJsonData={
214 "basicAuthPassword": await get_connector_attribute(
215 connector_id=1,
216 column_name="connector_password",
217 session=session,
218 ),
219 },
220 isDefault=False,
221 jsonData={
222 "database": f"sonicwall-{customer_code}*",
223 "flavor": "opensearch",
224 "includeFrozen": False,
225 "logLevelField": "severity",
226 "logMessageField": "summary",
227 "maxConcurrentShardRequests": 5,
228 "pplEnabled": True,
229 "timeField": "timestamp",
230 "tlsSkipVerify": True,
231 "version": await get_opensearch_version(),
232 },
233 readOnly=True,
234 )
235 results = grafana_client.datasource.create_datasource(
236 datasource=datasource_payload.model_dump(),
237 )
238 return GrafanaDataSourceCreationResponse(**results)
239
240
241 async def create_customer_network_connector_meta(
242 customer_details,
243 stream_id,
244 index_id,
245 content_pack_stream_id,
246 content_pack_input_id,
247 session,
248 ):
249 """
250 Create a CustomerNetworkConnectorsMeta object with the provided details.
251
252 Args:
253 customer_details (CustomerDetails): Details of the customer.
254 stream_id (int): ID of the Graylog stream.
255 index_id (int): ID of the Graylog index.
256 content_pack_stream_id (int): ID of the content pack stream.
257 content_pack_input_id (int): ID of the content pack input.
258 session (Session): Database session.
259
260 Returns:
261 CustomerNetworkConnectorsMeta: The created CustomerNetworkConnectorsMeta object.
262 """
263 return CustomerNetworkConnectorsMeta(
264 customer_code=customer_details.customer_code,
265 network_connector_name="SONICWALL",
266 graylog_stream_id=stream_id,
267 graylog_input_id=(
268 await get_input_id_by_input_name(input_name=f"{customer_details.customer_name} - SONICWALL FIREWALL LOGS AND EVENTS - TLS")
269 ),
270 graylog_pipeline_id=((await get_pipeline_id(subscription="SONICWALL"))[0]),
271 graylog_content_pack_input_id=content_pack_input_id,
272 graylog_content_pack_stream_id=content_pack_stream_id,
273 grafana_org_id=(
274 await get_customer_meta_attribute(
275 session=session,
276 customer_code=customer_details.customer_code,
277 column_name="customer_meta_grafana_org_id",
278 )
279 ),
280 graylog_index_id=index_id,
281 grafana_dashboard_folder_id=None,
282 grafana_datasource_uid=None,
283 )
284
285
286 async def validate_grafana_organization_id(customer_code, session):
287 """
288 Validate the Grafana organization ID for the customer.
289
290 Args:
291 customer_code (str): The customer code.
292 session (Session): Database session.
293
294 Returns:
295 int: The Grafana organization ID.
296 """
297 return await get_customer_meta_attribute(session=session, customer_code=customer_code, column_name="customer_meta_grafana_org_id")
298
299
300 async def provision_sonicwall(
301 customer_details: SonicwallCustomerDetails,
302 keys: ProvisionSonicwallKeys,
303 session: AsyncSession,
304 ) -> ProvisionSonicwallResponse:
305 """
306 Provisions a SonicWall customer by performing the following steps:
307 1. Validates Grafana organization ID exists.
308 2. Provisions the content pack for the customer.
309 3. Retrieves the stream and index IDs for the customer.
310 4. Creates customer network connector metadata.
311 5. Assigns the stream to the index.
312 6. Retrieves the pipeline ID for the "SONICWALL" subscription.
313 7. Connects the stream to the pipeline.
314 8. Creates Grafana datasource and dashboards.
315 9. Inserts the customer network connector metadata into the database.
316 10. Sets the deployed flag.
317
318 Args:
319 customer_details (SonicwallCustomerDetails): The details of the Sonicwall customer.
320 keys (ProvisionSonicwallKeys): The keys required for provisioning.
321 session (AsyncSession): The database session.
322
323 Returns:
324 ProvisionSonicwallResponse: Response indicating success or failure.
325 """
326 if await validate_grafana_organization_id(customer_details.customer_code, session) is None:
327 raise HTTPException(status_code=404, detail="Grafana organization ID not found. Please provision Grafana for the customer first.")
328
329 await provision_content_pack(customer_details)
330 stream_id, index_id, content_pack_stream_id, content_pack_input_id = await get_stream_and_index_ids(customer_details)
331 customer_network_connector_meta = await create_customer_network_connector_meta(
332 customer_details,
333 stream_id,
334 index_id,
335 content_pack_stream_id,
336 content_pack_input_id,
337 session,
338 )
339 await assign_stream_to_index(stream_id=stream_id, index_id=index_id)
340 pipeline_id = await get_pipeline_id(subscription="SONICWALL")
341 await connect_stream_to_pipeline(stream_and_pipeline=StreamConnectionToPipelineRequest(stream_id=stream_id, pipeline_ids=pipeline_id))
342
343 # Grafana Deployment
344 customer_network_connector_meta.grafana_datasource_uid = (
345 await create_grafana_datasource(
346 customer_code=customer_details.customer_code,
347 session=session,
348 )
349 ).datasource.uid
350 grafana_folder = await create_grafana_folder(
351 organization_id=(
352 await get_customer_meta(
353 customer_details.customer_code,
354 session,
355 )
356 ).customer_meta.customer_meta_grafana_org_id,
357 folder_title="SONICWALL",
358 )
359 await provision_dashboards(
360 DashboardProvisionRequest(
361 dashboards=[dashboard.name for dashboard in SonicwallDashboard],
362 organizationId=(
363 await get_customer_meta(
364 customer_details.customer_code,
365 session,
366 )
367 ).customer_meta.customer_meta_grafana_org_id,
368 folderId=grafana_folder.id,
369 datasourceUid=customer_network_connector_meta.grafana_datasource_uid,
370 ),
371 )
372 customer_network_connector_meta.grafana_dashboard_folder_id = grafana_folder.uid
373 await insert_into_customer_network_connectors_meta_table(
374 customer_network_connectors_meta=customer_network_connector_meta,
375 session=session,
376 )
377
378 await set_deployed_flag(
379 customer_code=customer_details.customer_code,
380 network_connector_service_name="Sonicwall",
381 flag=True,
382 session=session,
383 )
384
385 return ProvisionSonicwallResponse(
386 message="Sonicwall customer provisioned successfully",
387 success=True,
388 )
389
390
391 async def insert_into_customer_network_connectors_meta_table(
392 customer_network_connectors_meta: CustomerNetworkConnectorsMeta,
393 session: AsyncSession,
394 ) -> None:
395 """
396 Insert the customer network connectors meta into the database.
397
398 Args:
399 customer_network_connectors_meta (CustomerNetworkConnectorsMeta): The customer network connectors meta to insert.
400 session (AsyncSession): The async session object for database operations.
401
402 Returns:
403 None
404 """
405 logger.info("Inserting customer network connectors meta into the database")
406 session.add(customer_network_connectors_meta)
407 await session.commit()
408 logger.info("Customer network connectors meta inserted successfully")
409 return None