main
py 662 lines 24.5 KB
Raw
1 import json
2 from typing import Any
3 from typing import AsyncGenerator
4 from typing import Dict
5 from typing import Optional
6
7 from fastapi import APIRouter
8 from fastapi import BackgroundTasks
9 from fastapi import Depends
10 from fastapi import HTTPException
11 from fastapi import Query
12 from fastapi import Response
13 from fastapi import Security
14 from fastapi.responses import StreamingResponse
15 from loguru import logger
16 from sqlalchemy.ext.asyncio import AsyncSession
17
18 from app.agents.sca.schema.sca import ScaOverviewResponse
19 from app.agents.sca.schema.sca import ScaPackageAgentsResponse
20 from app.agents.sca.schema.sca import ScaPackageRegistryResponse
21 from app.agents.sca.schema.sca import ScaPoliciesIndexResponse
22 from app.agents.sca.schema.sca import ScaPolicyContentResponse
23 from app.agents.sca.schema.sca import SCAReportGenerateRequest
24 from app.agents.sca.schema.sca import SCAReportGenerateResponse
25 from app.agents.sca.schema.sca import SCAReportListResponse
26 from app.agents.sca.schema.sca import ScaStatsResponse
27 from app.agents.sca.services.sca import delete_sca_report
28 from app.agents.sca.services.sca import detect_agents_for_sca_package
29 from app.agents.sca.services.sca import fetch_sca_policies_index
30 from app.agents.sca.services.sca import fetch_sca_policy_content
31 from app.agents.sca.services.sca import generate_sca_csv_report
32 from app.agents.sca.services.sca import get_sca_report_download
33 from app.agents.sca.services.sca import get_sca_statistics
34 from app.agents.sca.services.sca import list_sca_package_registry
35 from app.agents.sca.services.sca import list_sca_reports
36 from app.agents.sca.services.sca import search_sca_overview
37 from app.agents.sca.services.sca import stream_sca_for_all_agents
38 from app.auth.models.users import User
39 from app.auth.routes.auth import AuthHandler
40 from app.db.db_session import get_db
41
42 # Create router for SCA overview endpoints
43 sca_router = APIRouter()
44
45
46 @sca_router.get(
47 "/overview",
48 response_model=ScaOverviewResponse,
49 description="Search SCA results across all agents with filtering and pagination",
50 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
51 )
52 async def search_sca_results_overview(
53 customer_code: Optional[str] = Query(None, description="Filter by customer code"),
54 agent_name: Optional[str] = Query(None, description="Filter by agent hostname"),
55 policy_id: Optional[str] = Query(None, description="Filter by specific policy ID"),
56 policy_name: Optional[str] = Query(None, description="Filter by policy name (partial matching)"),
57 min_score: Optional[int] = Query(None, description="Filter by minimum score (0-100)", ge=0, le=100),
58 max_score: Optional[int] = Query(None, description="Filter by maximum score (0-100)", ge=0, le=100),
59 page: int = Query(1, description="Page number for pagination", ge=1),
60 page_size: int = Query(50, description="Number of results per page", ge=1, le=1000),
61 db: AsyncSession = Depends(get_db),
62 ) -> ScaOverviewResponse:
63 """
64 Search SCA (Security Configuration Assessment) results across all agents.
65
66 This endpoint provides a comprehensive overview of SCA compliance across your
67 infrastructure by querying all agents and their SCA policy results.
68
69 **Features:**
70 - Real-time data collection from Wazuh Manager for all agents
71 - Advanced filtering by customer, agent, policy, and compliance scores
72 - Efficient pagination for large result sets
73 - Comprehensive statistics and aggregations
74 - No database storage required - direct from Wazuh Manager
75 - **Intelligent sorting: Results are sorted by agent minimum score (lowest first)**
76
77 **Use Cases:**
78 - Get organization-wide SCA compliance overview
79 - Identify agents with poor compliance scores
80 - Monitor specific security policies across all systems
81 - Track compliance trends and improvements
82
83 **Performance:**
84 - Efficiently queries multiple agents in parallel where possible
85 - Automatic error handling for unavailable agents
86 - Optimized data collection and processing
87 - Smart filtering to reduce data transfer
88 - **Smart sorting: Agents with lowest compliance scores appear first for priority attention**
89
90 **Filtering Options:**
91 - **customer_code**: Filter by specific customer/organization
92 - **agent_name**: Filter by specific agent hostname
93 - **policy_id**: Search for specific policy ID (exact match)
94 - **policy_name**: Filter by policy name (supports partial matching)
95 - **min_score**: Filter by minimum compliance score (0-100)
96 - **max_score**: Filter by maximum compliance score (0-100)
97
98 **Response Statistics:**
99 - **total_agents**: Number of unique agents with SCA data
100 - **total_policies**: Number of unique policies across all agents
101 - **average_score**: Average compliance score across all results
102 - **total_checks/passes/fails/invalid**: Aggregated counts across all agents
103
104 **Pagination:**
105 - **page**: Page number (starts at 1)
106 - **page_size**: Results per page (1-1000, default: 50)
107
108 Args:
109 customer_code: Optional customer code filter
110 agent_name: Optional agent hostname filter
111 policy_id: Optional policy ID filter (exact match)
112 policy_name: Optional policy name filter (partial matching)
113 min_score: Optional minimum compliance score filter
114 max_score: Optional maximum compliance score filter
115 page: Page number for pagination
116 page_size: Number of results per page
117 db: Database session
118
119 Returns:
120 ScaOverviewResponse: Paginated SCA results with comprehensive statistics
121 """
122 logger.info(
123 f"Searching SCA overview with filters: "
124 f"customer_code={customer_code}, agent_name={agent_name}, "
125 f"policy_id={policy_id}, policy_name={policy_name}, "
126 f"min_score={min_score}, max_score={max_score}, "
127 f"page={page}, page_size={page_size}",
128 )
129
130 try:
131 result = await search_sca_overview(
132 db_session=db,
133 customer_code=customer_code,
134 agent_name=agent_name,
135 policy_id=policy_id,
136 policy_name=policy_name,
137 min_score=min_score,
138 max_score=max_score,
139 page=page,
140 page_size=page_size,
141 )
142 return result
143
144 except Exception as e:
145 logger.error(f"Error in SCA overview search endpoint: {e}")
146 raise HTTPException(status_code=500, detail=f"Failed to search SCA results: {e}")
147
148
149 @sca_router.get(
150 "/overview/stream",
151 description="Stream SCA results across all agents as they are collected",
152 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
153 )
154 async def stream_sca_results_overview(
155 customer_code: Optional[str] = Query(None, description="Filter by customer code"),
156 agent_name: Optional[str] = Query(None, description="Filter by agent hostname"),
157 policy_id: Optional[str] = Query(None, description="Filter by specific policy ID"),
158 policy_name: Optional[str] = Query(None, description="Filter by policy name (partial matching)"),
159 min_score: Optional[int] = Query(None, description="Filter by minimum score (0-100)", ge=0, le=100),
160 max_score: Optional[int] = Query(None, description="Filter by maximum score (0-100)", ge=0, le=100),
161 db: AsyncSession = Depends(get_db),
162 ) -> StreamingResponse:
163 """
164 Stream SCA results as Server-Sent Events (SSE).
165
166 Results are sent as they are collected from each agent, allowing the frontend
167 to display data progressively without waiting for all agents to complete.
168
169 **Event Types:**
170 - `start`: Initial event with total agent count
171 - `agent_result`: SCA results for a single agent
172 - `agent_error`: Error collecting data from an agent
173 - `progress`: Progress update (agents processed so far)
174 - `complete`: Final event with summary statistics
175 - `error`: Fatal error that stops the stream
176
177 **Example Events:**
178 ```
179 event: start
180 data: {"total_agents": 50, "message": "Starting SCA collection..."}
181
182 event: agent_result
183 data: {"agent_id": "001", "agent_name": "server1", "policies": [...]}
184
185 event: progress
186 data: {"processed": 10, "total": 50, "successful": 8, "failed": 2}
187
188 event: complete
189 data: {"total_results": 150, "total_agents": 50, "average_score": 78.5, ...}
190 ```
191 """
192 logger.info(
193 f"Streaming SCA overview with filters: "
194 f"customer_code={customer_code}, agent_name={agent_name}, "
195 f"policy_id={policy_id}, policy_name={policy_name}, "
196 f"min_score={min_score}, max_score={max_score}",
197 )
198
199 async def event_generator() -> AsyncGenerator[str, None]:
200 try:
201 async for event in stream_sca_for_all_agents(
202 db_session=db,
203 customer_code=customer_code,
204 agent_name=agent_name,
205 policy_id=policy_id,
206 policy_name=policy_name,
207 min_score=min_score,
208 max_score=max_score,
209 ):
210 # Format as SSE
211 event_type = event.get("event", "message")
212 data = json.dumps(event.get("data", {}))
213 yield f"event: {event_type}\ndata: {data}\n\n"
214 except Exception as e:
215 logger.error(f"Error in SSE stream: {e}")
216 error_data = json.dumps({"error": str(e), "message": "Stream error occurred"})
217 yield f"event: error\ndata: {error_data}\n\n"
218
219 return StreamingResponse(
220 event_generator(),
221 media_type="text/event-stream",
222 headers={
223 "Cache-Control": "no-cache",
224 "Connection": "keep-alive",
225 "X-Accel-Buffering": "no", # Disable nginx buffering
226 },
227 )
228
229
230 @sca_router.get(
231 "/stats",
232 response_model=ScaStatsResponse,
233 description="Get SCA statistics across all agents or for a specific customer",
234 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
235 )
236 async def get_sca_stats(
237 customer_code: Optional[str] = Query(None, description="Filter by customer code"),
238 db: AsyncSession = Depends(get_db),
239 ) -> ScaStatsResponse:
240 """
241 Get comprehensive SCA (Security Configuration Assessment) statistics.
242
243 This endpoint provides high-level statistics about SCA compliance across
244 your infrastructure, helping you understand overall security posture.
245
246 **Features:**
247 - Organization-wide or customer-specific statistics
248 - Real-time data collection from all agents
249 - Aggregated compliance metrics
250 - Breakdown by customer when viewing all data
251
252 **Statistics Provided:**
253 - **total_agents_with_sca**: Number of agents that have SCA data
254 - **total_policies**: Number of unique security policies across all agents
255 - **average_score_across_all**: Overall average compliance score
256 - **total_checks/passes/fails/invalid**: Sum of all checks across all agents
257 - **by_customer**: Detailed breakdown when viewing all customers
258
259 **Use Cases:**
260 - Executive dashboards and reporting
261 - Compliance trend monitoring
262 - Cross-customer comparison (for MSPs)
263 - Infrastructure security health checks
264
265 Args:
266 customer_code: Optional customer code to filter statistics by
267 db: Database session
268
269 Returns:
270 ScaStatsResponse: Comprehensive SCA statistics
271 """
272 logger.info(f"Getting SCA statistics for customer: {customer_code or 'all customers'}")
273
274 try:
275 result = await get_sca_statistics(db_session=db, customer_code=customer_code)
276 return result
277
278 except Exception as e:
279 logger.error(f"Error getting SCA statistics: {e}")
280 raise HTTPException(status_code=500, detail=f"Failed to get SCA statistics: {e}")
281
282
283 @sca_router.post(
284 "/reports/generate",
285 response_model=SCAReportGenerateResponse,
286 description="Generate a CSV report of SCA results",
287 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
288 )
289 async def generate_sca_report(
290 request: SCAReportGenerateRequest,
291 background_tasks: BackgroundTasks,
292 db: AsyncSession = Depends(get_db),
293 current_user: User = Security(AuthHandler().get_current_user),
294 ) -> SCAReportGenerateResponse:
295 """
296 Generate a comprehensive CSV report of SCA (Security Configuration Assessment) results.
297
298 This endpoint creates a downloadable CSV file containing all SCA policy results
299 matching the specified criteria. The report is generated in the background and
300 stored in MinIO for later download.
301
302 **Features:**
303 - Generates comprehensive CSV reports with all SCA policy data
304 - Background processing for large datasets
305 - Stores reports in MinIO for persistent access
306 - Filters applied are saved with the report
307 - Tracks report generation status (processing, completed, failed)
308 - Customer access control enforced
309
310 **Report Contents:**
311 The CSV report includes the following columns:
312 - Agent ID
313 - Agent Name
314 - Customer Code
315 - Policy ID
316 - Policy Name
317 - Description
318 - Total Checks
319 - Passed
320 - Failed
321 - Invalid
322 - Score
323 - Start Scan
324 - End Scan
325 - References
326 - Hash File
327
328 **Use Cases:**
329 - Export compliance data for external analysis
330 - Generate reports for compliance audits
331 - Share security posture with stakeholders
332 - Integrate with third-party tools
333 - Historical record keeping
334
335 **Filtering Options:**
336 - **customer_code** (required): Customer to generate report for
337 - **report_name** (optional): Custom name for the report
338 - **agent_name**: Filter by specific agent hostname
339 - **policy_id**: Filter by specific policy ID
340 - **min_score**: Filter by minimum compliance score
341 - **max_score**: Filter by maximum compliance score
342
343 **Report Statistics:**
344 - **total_policies**: Number of policy results included
345 - **total_checks**: Sum of all checks across policies
346 - **passed_count**: Total passed checks
347 - **failed_count**: Total failed checks
348 - **invalid_count**: Total invalid checks
349
350 **Background Processing:**
351 - Report generation starts immediately in background
352 - Status tracked in database (processing → completed/failed)
353 - Use `/reports` endpoint to check generation status
354 - Download via `/reports/{id}/download` when completed
355
356 Args:
357 request: Report generation request with filters
358 background_tasks: FastAPI background tasks for async generation
359 db: Database session
360 current_user: Current authenticated user
361
362 Returns:
363 SCAReportGenerateResponse: Report generation status and details
364 """
365 logger.info(f"Generating SCA report for customer {request.customer_code} " f"with filters: {request.model_dump(exclude_none=True)}")
366
367 try:
368 # Note: This will be processed synchronously for now
369 # For true background processing, implement background task pattern
370 result = await generate_sca_csv_report(
371 db_session=db,
372 current_user=current_user,
373 request=request,
374 )
375 return result
376
377 except Exception as e:
378 logger.error(f"Error generating SCA report: {e}")
379 raise HTTPException(status_code=500, detail=f"Failed to generate report: {e}")
380
381
382 @sca_router.get(
383 "/reports",
384 response_model=SCAReportListResponse,
385 description="List available SCA reports",
386 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
387 )
388 async def list_reports(
389 customer_code: Optional[str] = Query(None, description="Filter by customer code"),
390 db: AsyncSession = Depends(get_db),
391 current_user: User = Security(AuthHandler().get_current_user),
392 ) -> SCAReportListResponse:
393 """
394 List all available SCA reports.
395
396 This endpoint returns all SCA reports that the current user has access to,
397 with optional filtering by customer code.
398
399 **Features:**
400 - Lists all generated reports with metadata
401 - Respects customer access permissions
402 - Shows report status (processing, completed, failed)
403 - Includes generation details and statistics
404 - Sorted by generation time (newest first)
405
406 **Report Information:**
407 - **id**: Unique report identifier
408 - **report_name**: Name of the report
409 - **customer_code**: Customer the report belongs to
410 - **file_name**: CSV filename
411 - **file_size**: Size in bytes
412 - **generated_at**: Timestamp when report was generated
413 - **generated_by**: User ID who generated the report
414 - **status**: Current status (processing/completed/failed)
415 - **total_policies**: Number of policy results in report
416 - **total_checks**: Sum of all checks
417 - **passed/failed/invalid_count**: Check result breakdowns
418 - **filters_applied**: Filters used during generation
419 - **download_url**: Endpoint to download the report
420
421 **Use Cases:**
422 - View all available reports
423 - Check report generation status
424 - Find specific reports by customer
425 - Monitor report history
426
427 Args:
428 customer_code: Optional customer code filter
429 db: Database session
430 current_user: Current authenticated user
431
432 Returns:
433 SCAReportListResponse: List of available reports
434 """
435 logger.info(f"Listing SCA reports for customer: {customer_code or 'all accessible'}")
436
437 try:
438 result = await list_sca_reports(
439 db_session=db,
440 current_user=current_user,
441 customer_code=customer_code,
442 )
443 return result
444
445 except Exception as e:
446 logger.error(f"Error listing SCA reports: {e}")
447 raise HTTPException(status_code=500, detail=f"Failed to list reports: {e}")
448
449
450 @sca_router.get(
451 "/reports/{report_id}/download",
452 description="Download an SCA report",
453 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
454 )
455 async def download_report(
456 report_id: int,
457 db: AsyncSession = Depends(get_db),
458 current_user: User = Security(AuthHandler().get_current_user),
459 ) -> Response:
460 """
461 Download a specific SCA report as CSV.
462
463 This endpoint retrieves a previously generated SCA report from MinIO and
464 returns it as a downloadable CSV file.
465
466 **Features:**
467 - Downloads report as CSV file
468 - Verifies customer access permissions
469 - Returns proper CSV content type
470 - Includes filename in response headers
471
472 **Use Cases:**
473 - Download reports for analysis
474 - Share reports with stakeholders
475 - Import into other tools
476 - Archive compliance records
477
478 **Access Control:**
479 - Users can only download reports for customers they have access to
480 - Admin users can download any report
481 - Report ID must exist and belong to an accessible customer
482
483 Args:
484 report_id: ID of the report to download
485 db: Database session
486 current_user: Current authenticated user
487
488 Returns:
489 Response: CSV file download
490 """
491 logger.info(f"Downloading SCA report: {report_id}")
492
493 try:
494 result = await get_sca_report_download(
495 db_session=db,
496 current_user=current_user,
497 report_id=report_id,
498 )
499
500 return Response(
501 content=result["file_content"],
502 media_type=result["content_type"],
503 headers={
504 "Content-Disposition": f"attachment; filename={result['file_name']}",
505 },
506 )
507
508 except HTTPException:
509 raise
510 except Exception as e:
511 logger.error(f"Error downloading SCA report: {e}")
512 raise HTTPException(status_code=500, detail=f"Failed to download report: {e}")
513
514
515 @sca_router.delete(
516 "/reports/{report_id}",
517 description="Delete an SCA report",
518 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
519 )
520 async def delete_report(
521 report_id: int,
522 db: AsyncSession = Depends(get_db),
523 current_user: User = Security(AuthHandler().get_current_user),
524 ) -> Dict[str, Any]:
525 """
526 Delete a specific SCA report.
527
528 This endpoint deletes both the report record from the database and the
529 associated CSV file from MinIO storage.
530
531 **Features:**
532 - Deletes report from database
533 - Removes CSV file from MinIO
534 - Verifies customer access permissions
535 - Graceful handling if MinIO file is already deleted
536
537 **Use Cases:**
538 - Clean up old or unnecessary reports
539 - Remove reports with errors
540 - Free up storage space
541 - Manage report lifecycle
542
543 **Access Control:**
544 - Users can only delete reports for customers they have access to
545 - Admin users can delete any report
546 - Report ID must exist and belong to an accessible customer
547
548 **Behavior:**
549 - Deletes the database record
550 - Attempts to delete the file from MinIO
551 - If MinIO deletion fails, still removes database record (file may be orphaned)
552 - Returns success if database deletion succeeds
553
554 Args:
555 report_id: ID of the report to delete
556 db: Database session
557 current_user: Current authenticated user
558
559 Returns:
560 Dict with success status and message
561 """
562 logger.info(f"Deleting SCA report: {report_id}")
563
564 try:
565 result = await delete_sca_report(
566 db_session=db,
567 current_user=current_user,
568 report_id=report_id,
569 )
570 return result
571
572 except HTTPException:
573 raise
574 except Exception as e:
575 logger.error(f"Error in delete report endpoint: {e}")
576 raise HTTPException(status_code=500, detail=f"Failed to delete report: {e}")
577
578
579 @sca_router.get(
580 "/policies",
581 response_model=ScaPoliciesIndexResponse,
582 description="List all available SCA policies from the CoPilot-SCA repository",
583 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
584 )
585 async def list_available_sca_policies() -> ScaPoliciesIndexResponse:
586 """
587 List all available SCA (Security Configuration Assessment) policies from the
588 public CoPilot-SCA GitHub repository.
589
590 This endpoint fetches the repository index and returns metadata for every
591 policy that can be deployed, including its name, description, target
592 application, platform, and CIS benchmark version.
593
594 **Use Cases:**
595 - Browse available CIS benchmark policies
596 - Discover policies for a specific application or platform
597 - Review available policy versions before deployment
598 """
599 return await fetch_sca_policies_index()
600
601
602 @sca_router.get(
603 "/policies/{policy_id}",
604 response_model=ScaPolicyContentResponse,
605 description="Fetch the YAML content of a specific SCA policy",
606 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
607 )
608 async def get_sca_policy_content(policy_id: str) -> ScaPolicyContentResponse:
609 """
610 Fetch the raw YAML content of a single SCA policy from the public
611 CoPilot-SCA GitHub repository.
612
613 The ``policy_id`` must match one of the identifiers returned by the
614 ``/policies`` listing endpoint (e.g. ``cis_apache_24_rpm``).
615
616 **Use Cases:**
617 - Preview the full YAML of a policy before deploying it
618 - Review the checks included in a specific CIS benchmark
619 - Download policy content for offline analysis
620 """
621 return await fetch_sca_policy_content(policy_id)
622
623
624 @sca_router.get(
625 "/packages/registry",
626 response_model=ScaPackageRegistryResponse,
627 description="List all tracked SCA-relevant package categories",
628 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
629 )
630 async def get_sca_package_registry() -> ScaPackageRegistryResponse:
631 """
632 Return every entry in the SCA package registry.
633
634 Each entry maps an application category (e.g. ``apache``, ``mysql``) to
635 the package name patterns that indicate the software is installed on an
636 agent. Use the ``key`` value with the ``/packages/registry/{key}/agents``
637 endpoint to discover which agents have that software.
638 """
639 return await list_sca_package_registry()
640
641
642 @sca_router.get(
643 "/packages/registry/{registry_key}/agents",
644 response_model=ScaPackageAgentsResponse,
645 description="Detect agents running a tracked SCA-relevant package",
646 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
647 )
648 async def get_agents_for_sca_package(registry_key: str) -> ScaPackageAgentsResponse:
649 """
650 Given a registry key (e.g. ``apache``, ``nginx``, ``mysql``), search the
651 Wazuh Indexer for agents that have any matching packages installed.
652
653 The response also includes the list of SCA policies from the CoPilot-SCA
654 repository that are applicable to that application, making it easy to see
655 which benchmarks can be deployed to the discovered agents.
656
657 **Use Cases:**
658 - Identify all agents running Apache to deploy the CIS Apache benchmark
659 - Find agents with MySQL/MariaDB for targeted SCA policy deployment
660 - Audit which agents would benefit from a specific SCA policy
661 """
662 return await detect_agents_for_sca_package(registry_key)