main
py 128 lines 5.39 KB
Raw
1 from typing import List
2 from typing import Optional
3
4 from fastapi import APIRouter
5 from fastapi import Depends
6 from fastapi import HTTPException
7 from fastapi import Query
8 from fastapi import Security
9 from loguru import logger
10 from sqlalchemy import select as sa_select
11 from sqlalchemy.ext.asyncio import AsyncSession
12
13 from app.agents.wazuh.syscollector.schema.packages import AgentPackagesResponse
14 from app.agents.wazuh.syscollector.schema.packages import IndexerPackagesResponse
15 from app.agents.wazuh.syscollector.services.packages import collect_agent_packages
16 from app.agents.wazuh.syscollector.services.packages import search_packages_in_indexer
17 from app.auth.models.users import User
18 from app.auth.routes.auth import AuthHandler
19 from app.db.db_session import get_db
20 from app.db.universal_models import Agents
21 from app.middleware.customer_access import customer_access_handler
22
23 packages_router = APIRouter()
24
25
26 @packages_router.get(
27 "/search/packages",
28 response_model=IndexerPackagesResponse,
29 description="Search installed packages across all agents via the Wazuh Indexer",
30 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
31 )
32 async def search_packages(
33 package_name: Optional[str] = Query(None, description="Filter by package name (wildcard match)"),
34 agent_name: Optional[str] = Query(None, description="Filter by agent name (wildcard match)"),
35 agent_id: Optional[str] = Query(None, description="Filter by agent ID (exact match)"),
36 architecture: Optional[str] = Query(None, description="Filter by architecture (e.g. amd64, x86_64)"),
37 package_type: Optional[str] = Query(None, description="Filter by package type (e.g. deb, rpm)"),
38 vendor: Optional[str] = Query(None, description="Filter by vendor (wildcard match)"),
39 package_version: Optional[str] = Query(None, description="Filter by package version (wildcard match)"),
40 size: int = Query(500, ge=1, le=10000, description="Maximum number of results to return"),
41 ) -> IndexerPackagesResponse:
42 """
43 Search the Wazuh Indexer for package inventory data across all agents.
44
45 Unlike the per-agent endpoint that queries the Wazuh Manager API, this
46 endpoint queries the ``wazuh-states-inventory-packages-*`` index in the
47 Wazuh Indexer (OpenSearch), allowing you to search for packages across
48 every agent without specifying an agent ID.
49
50 **Use Cases:**
51 - Find all agents that have a specific package installed
52 - Search for outdated versions of a package across the fleet
53 - Inventory packages by type, architecture, or vendor
54 """
55 return await search_packages_in_indexer(
56 package_name=package_name,
57 agent_name=agent_name,
58 agent_id=agent_id,
59 architecture=architecture,
60 package_type=package_type,
61 vendor=vendor,
62 package_version=package_version,
63 size=size,
64 )
65
66
67 @packages_router.get(
68 "/{agent_id}/packages",
69 response_model=AgentPackagesResponse,
70 description="Get installed packages for a specific agent from the Wazuh Manager syscollector",
71 dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst", "customer_user"))],
72 )
73 async def get_agent_packages(
74 agent_id: str,
75 limit: int = Query(500, ge=1, le=100000, description="Maximum number of packages to return"),
76 offset: int = Query(0, ge=0, description="First element to return"),
77 sort: Optional[str] = Query(None, description="Sort by field(s). Use +/- prefix for asc/desc order"),
78 search: Optional[str] = Query(None, description="Free-text search string"),
79 select: Optional[List[str]] = Query(None, description="Fields to return"),
80 vendor: Optional[str] = Query(None, description="Filter by vendor"),
81 name: Optional[str] = Query(None, description="Filter by package name"),
82 architecture: Optional[str] = Query(None, description="Filter by architecture"),
83 format: Optional[str] = Query(None, alias="format", description="Filter by package format (e.g. deb, rpm)"),
84 version: Optional[str] = Query(None, description="Filter by package version"),
85 q: Optional[str] = Query(None, description='Advanced query filter (e.g. q="name=openssl")'),
86 current_user: User = Depends(AuthHandler().get_current_user),
87 session: AsyncSession = Depends(get_db),
88 ) -> AgentPackagesResponse:
89 """
90 Fetch installed packages for a specific agent via the Wazuh Manager
91 syscollector API.
92
93 Returns package name, version, architecture, vendor, format, and other
94 metadata for every installed package on the agent.
95 """
96 logger.info(f"Fetching packages for agent {agent_id}")
97
98 # Verify the user has access to this agent's customer
99 base_query = sa_select(Agents).filter(Agents.agent_id == agent_id)
100 filtered_query = await customer_access_handler.filter_query_by_customer_access(
101 current_user,
102 session,
103 base_query,
104 Agents.customer_code,
105 )
106 result = await session.execute(filtered_query)
107 agent = result.scalars().first()
108
109 if not agent:
110 raise HTTPException(
111 status_code=404,
112 detail=f"Agent with agent_id {agent_id} not found or access denied",
113 )
114
115 return await collect_agent_packages(
116 agent_id=agent_id,
117 limit=limit,
118 offset=offset,
119 sort=sort,
120 search=search,
121 select=select,
122 vendor=vendor,
123 name=name,
124 architecture=architecture,
125 format=format,
126 version=version,
127 q=q,
128 )