main
py 399 lines 13.6 KB
Raw
1 import json
2 from datetime import datetime
3
4 from loguru import logger
5 from sqlalchemy import and_
6 from sqlalchemy import update
7 from sqlalchemy.ext.asyncio import AsyncSession
8
9 from app.connectors.grafana.schema.dashboards import DashboardProvisionRequest
10 from app.connectors.grafana.schema.dashboards import DuoDashboard
11 from app.connectors.grafana.services.dashboards import provision_dashboards
12 from app.connectors.grafana.utils.universal import create_grafana_client
13 from app.connectors.graylog.services.management import start_stream
14 from app.connectors.graylog.utils.universal import send_post_request
15 from app.connectors.graylog.utils.universal import send_post_request_create_entity
16 from app.connectors.wazuh_indexer.services.monitoring import (
17 output_shard_number_to_be_set_based_on_nodes,
18 )
19 from app.customer_provisioning.schema.grafana import GrafanaDatasource
20 from app.customer_provisioning.schema.grafana import GrafanaDataSourceCreationResponse
21 from app.customer_provisioning.schema.graylog import GraylogIndexSetCreationResponse
22 from app.customer_provisioning.schema.graylog import StreamCreationResponse
23 from app.customer_provisioning.schema.graylog import TimeBasedIndexSet
24 from app.customer_provisioning.services.grafana import create_grafana_folder
25 from app.customer_provisioning.services.grafana import get_opensearch_version
26 from app.customers.routes.customers import get_customer
27 from app.customers.routes.customers import get_customer_meta
28 from app.integrations.duo.schema.provision import DuoEventStream
29 from app.integrations.duo.schema.provision import ProvisionDuoRequest
30 from app.integrations.duo.schema.provision import ProvisionDuoResponse
31 from app.integrations.models.customer_integration_settings import CustomerIntegrations
32 from app.integrations.routes import create_integration_meta
33 from app.integrations.schema import CustomerIntegrationsMetaSchema
34 from app.utils import get_connector_attribute
35
36
37 ################## ! GRAYLOG ! ##################
38 async def build_index_set_config(
39 customer_code: str,
40 session: AsyncSession,
41 ) -> TimeBasedIndexSet:
42 """
43 Build the configuration for a time-based index set.
44
45 Args:
46 request (ProvisionNewCustomer): The request object containing customer information.
47
48 Returns:
49 TimeBasedIndexSet: The configured time-based index set.
50 """
51 return TimeBasedIndexSet(
52 title=f"{(await get_customer(customer_code, session)).customer.customer_name} - DUO",
53 description=f"{customer_code} - DUO",
54 index_prefix=f"duo-{customer_code.lower()}",
55 rotation_strategy_class="org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategy",
56 rotation_strategy={
57 "type": "org.graylog2.indexer.rotation.strategies.TimeBasedRotationStrategyConfig",
58 "rotation_period": "P1D",
59 "rotate_empty_index_set": False,
60 "max_rotation_period": None,
61 },
62 retention_strategy_class="org.graylog2.indexer.retention.strategies.DeletionRetentionStrategy",
63 retention_strategy={
64 "type": "org.graylog2.indexer.retention.strategies.DeletionRetentionStrategyConfig",
65 "max_number_of_indices": 30,
66 },
67 creation_date=datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ"),
68 index_analyzer="standard",
69 shards=await output_shard_number_to_be_set_based_on_nodes(),
70 replicas=0,
71 index_optimization_max_num_segments=1,
72 index_optimization_disabled=False,
73 writable=True,
74 field_type_refresh_interval=5000,
75 )
76
77
78 # Function to send the POST request and handle the response
79 async def send_index_set_creation_request(
80 index_set: TimeBasedIndexSet,
81 ) -> GraylogIndexSetCreationResponse:
82 """
83 Sends a request to create an index set in Graylog.
84
85 Args:
86 index_set (TimeBasedIndexSet): The index set to be created.
87
88 Returns:
89 GraylogIndexSetCreationResponse: The response from Graylog after creating the index set.
90 """
91 json_index_set = json.dumps(index_set.model_dump())
92 logger.info(f"json_index_set set: {json_index_set}")
93 response_json = await send_post_request(
94 endpoint="/api/system/indices/index_sets",
95 data=index_set.model_dump(),
96 )
97 return GraylogIndexSetCreationResponse(**response_json)
98
99
100 async def create_index_set(
101 customer_code: str,
102 session: AsyncSession,
103 ) -> GraylogIndexSetCreationResponse:
104 """
105 Creates an index set for a new customer.
106
107 Args:
108 request (ProvisionNewCustomer): The request object containing the customer information.
109
110 Returns:
111 GraylogIndexSetCreationResponse: The response object containing the result of the index set creation.
112 """
113 logger.info(f"Creating index set for customer {customer_code}")
114 index_set_config = await build_index_set_config(customer_code, session)
115 return await send_index_set_creation_request(index_set_config)
116
117
118 # ! Event STREAMS ! #
119 # Function to create event stream configuration
120 async def build_event_stream_config(
121 customer_code: str,
122 index_set_id: str,
123 session: AsyncSession,
124 ) -> DuoEventStream:
125 """
126 Builds the configuration for the Duo event stream.
127
128 Args:
129 customer_code (str): The customer code.
130 index_set_id (str): The index set ID.
131 session (AsyncSession): The async session.
132
133 Returns:
134 DuoEventStream: The configured Duo event stream.
135 """
136 return DuoEventStream(
137 title=f"{(await get_customer(customer_code, session)).customer.customer_name} - DUO",
138 description=f"{(await get_customer(customer_code, session)).customer.customer_name} - DUO",
139 index_set_id=index_set_id,
140 rules=[
141 {
142 "field": "integration",
143 "type": 1,
144 "inverted": False,
145 "value": "duo",
146 },
147 {
148 "field": "customer_code",
149 "type": 1,
150 "inverted": False,
151 "value": f"{customer_code}",
152 },
153 ],
154 matching_type="AND",
155 remove_matches_from_default_stream=True,
156 content_pack=None,
157 )
158
159
160 async def send_event_stream_creation_request(
161 event_stream: DuoEventStream,
162 ) -> StreamCreationResponse:
163 """
164 Sends a request to create an event stream.
165
166 Args:
167 event_stream (SapSiemEventStream): The event stream to be created.
168
169 Returns:
170 StreamCreationResponse: The response containing the created event stream.
171 """
172 json_event_stream = json.dumps(event_stream.model_dump())
173 logger.info(f"json_event_stream set: {json_event_stream}")
174 response_json = await send_post_request_create_entity(
175 endpoint="/api/streams",
176 entity=event_stream.model_dump(),
177 )
178 return StreamCreationResponse(**response_json)
179
180
181 async def create_event_stream(
182 customer_code: str,
183 index_set_id: str,
184 session: AsyncSession,
185 ) -> StreamCreationResponse:
186 """
187 Creates an event stream for a customer.
188
189 Args:
190 request (ProvisionNewCustomer): The request object containing customer information.
191 index_set_id (str): The ID of the index set.
192
193 Returns:
194 The result of the event stream creation request.
195 """
196 event_stream_config = await build_event_stream_config(
197 customer_code,
198 index_set_id,
199 session,
200 )
201 return await send_event_stream_creation_request(event_stream_config)
202
203
204 #### ! GRAFANA ! ####
205 async def create_grafana_datasource(
206 customer_code: str,
207 session: AsyncSession,
208 ) -> GrafanaDataSourceCreationResponse:
209 """
210 Creates a Grafana datasource for the specified customer.
211
212 Args:
213 customer_code (str): The customer code.
214 session (AsyncSession): The async session.
215
216 Returns:
217 GrafanaDataSourceCreationResponse: The response containing the created datasource details.
218 """
219 logger.info("Creating Grafana datasource")
220 grafana_client = await create_grafana_client("Grafana")
221 # Switch to the newly created organization
222 grafana_client.user.switch_actual_user_organisation(
223 (await get_customer_meta(customer_code, session)).customer_meta.customer_meta_grafana_org_id,
224 )
225 datasource_payload = GrafanaDatasource(
226 name="DUO",
227 type="grafana-opensearch-datasource",
228 typeName="OpenSearch",
229 access="proxy",
230 url=await get_connector_attribute(
231 connector_id=1,
232 column_name="connector_url",
233 session=session,
234 ),
235 database=f"duo-{customer_code}*",
236 basicAuth=True,
237 basicAuthUser=await get_connector_attribute(
238 connector_id=1,
239 column_name="connector_username",
240 session=session,
241 ),
242 secureJsonData={
243 "basicAuthPassword": await get_connector_attribute(
244 connector_id=1,
245 column_name="connector_password",
246 session=session,
247 ),
248 },
249 isDefault=False,
250 jsonData={
251 "database": f"duo-{customer_code}*",
252 "flavor": "opensearch",
253 "includeFrozen": False,
254 "logLevelField": "severity",
255 "logMessageField": "summary",
256 "maxConcurrentShardRequests": 5,
257 "pplEnabled": True,
258 "timeField": "timestamp",
259 "tlsSkipVerify": True,
260 "version": await get_opensearch_version(),
261 },
262 readOnly=True,
263 )
264 results = grafana_client.datasource.create_datasource(
265 datasource=datasource_payload.model_dump(),
266 )
267 return GrafanaDataSourceCreationResponse(**results)
268
269
270 async def provision_duo(
271 provision_duo_request: ProvisionDuoRequest,
272 session: AsyncSession,
273 ) -> ProvisionDuoResponse:
274 logger.info(
275 f"Provisioning Duo integration for customer {provision_duo_request.customer_code}.",
276 )
277
278 # Create Index Set
279 index_set_id = (
280 await create_index_set(
281 customer_code=provision_duo_request.customer_code,
282 session=session,
283 )
284 ).data.id
285 logger.info(f"Index set: {index_set_id}")
286 # Create event stream
287 stream_id = (
288 await create_event_stream(
289 provision_duo_request.customer_code,
290 index_set_id,
291 session,
292 )
293 ).data.stream_id
294 # Start stream
295 await start_stream(stream_id=stream_id)
296
297 # Grafana Deployment
298 duo_datasource_uid = (
299 await create_grafana_datasource(
300 customer_code=provision_duo_request.customer_code,
301 session=session,
302 )
303 ).datasource.uid
304 grafana_duo_folder_id = (
305 await create_grafana_folder(
306 organization_id=(
307 await get_customer_meta(
308 provision_duo_request.customer_code,
309 session,
310 )
311 ).customer_meta.customer_meta_grafana_org_id,
312 folder_title="DUO",
313 )
314 ).id
315 await provision_dashboards(
316 DashboardProvisionRequest(
317 dashboards=[dashboard.name for dashboard in DuoDashboard],
318 organizationId=(
319 await get_customer_meta(
320 provision_duo_request.customer_code,
321 session,
322 )
323 ).customer_meta.customer_meta_grafana_org_id,
324 folderId=grafana_duo_folder_id,
325 datasourceUid=duo_datasource_uid,
326 ),
327 )
328 await create_integration_meta_entry(
329 CustomerIntegrationsMetaSchema(
330 customer_code=provision_duo_request.customer_code,
331 integration_name="Duo",
332 graylog_input_id=None,
333 graylog_index_id=index_set_id,
334 graylog_stream_id=stream_id,
335 grafana_org_id=(
336 await get_customer_meta(
337 provision_duo_request.customer_code,
338 session,
339 )
340 ).customer_meta.customer_meta_grafana_org_id,
341 grafana_dashboard_folder_id=grafana_duo_folder_id,
342 grafana_datasource_uid=duo_datasource_uid,
343 ),
344 session,
345 )
346 await update_customer_integration_table(
347 provision_duo_request.customer_code,
348 session,
349 )
350
351 return ProvisionDuoResponse(
352 success=True,
353 message="Duo integration provisioned successfully.",
354 )
355
356
357 ############## ! WRITE TO DB ! ##############
358 async def create_integration_meta_entry(
359 customer_integration_meta: CustomerIntegrationsMetaSchema,
360 session: AsyncSession,
361 ) -> None:
362 """
363 Creates an entry for the customer integration meta in the database.
364
365 Args:
366 customer_integration_meta (CustomerIntegrationsMetaSchema): The customer integration meta object.
367 session (AsyncSession): The async session object for database operations.
368 """
369 await create_integration_meta(customer_integration_meta, session)
370 logger.info(
371 f"Integration meta entry created for customer {customer_integration_meta.customer_code}.",
372 )
373
374
375 async def update_customer_integration_table(
376 customer_code: str,
377 session: AsyncSession,
378 ) -> None:
379 """
380 Updates the `customer_integrations` table to set the `deployed` column to True where the `customer_code`
381 matches the given customer code and the `integration_service_name` is "Duo".
382
383 Args:
384 customer_code (str): The customer code.
385 session (AsyncSession): The async session object for making HTTP requests.
386 """
387 await session.execute(
388 update(CustomerIntegrations)
389 .where(
390 and_(
391 CustomerIntegrations.customer_code == customer_code,
392 CustomerIntegrations.integration_service_name == "Duo",
393 ),
394 )
395 .values(deployed=True),
396 )
397 await session.commit()
398
399 return None