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