@cryptotaxi247 / CoPilot / commits / 33bc8113

Manual update agent client (#217)

* feat: Add optional fields for Velociraptor agent in Agents model The code changes in `universal_models.py` modify the `Agents` model to include optional fields for the Velociraptor agent. The `velociraptor_id` and `velociraptor_last_seen` fields are now optional, allowing for cases where the Velociraptor agent is not present. Similarly, the `velociraptor_agent_version` field is also made optional. This change improves the flexibility of the model and accommodates scenarios where the Velociraptor agent may not be used. Note: This commit message follows the established convention of using a prefix to indicate the type of change (`feat` for a new feature) and provides a clear and concise description of the changes made. * agent rewrite * feat: Add endpoint to update agent's Velociraptor ID The code changes in `agents.py` add a new endpoint `/update` to update an agent's Velociraptor ID. This endpoint requires the `agent_id` and `velociraptor_id` as parameters and updates the corresponding agent's `velociraptor_id` field in the database. If the agent is not found, a 404 error is returned. This feature improves the functionality of the application by allowing users to easily update the Velociraptor ID of an agent. Note: This commit message follows the established convention of using a prefix to indicate the type of change (`feat` for a new feature) and provides a clear and concise description of the changes made. * feat: Refactor agent_sync to remove unnecessary session parameter The code changes in `agent_sync.py` remove the unnecessary `session` parameter from the `sync_all_agents` function. Since the function is now using the `get_db_session` context manager, there is no need to pass the session as a parameter. This refactor simplifies the code and improves readability. Note: This commit message follows the established convention of using a prefix to indicate the type of change (`feat` for a new feature) and provides a clear and concise description of the changes made. * updated dependencies * updated agent api * added AgentVelociraptorIdForm * precommit fixes --------- Co-authored-by: Davide Di Modica <webmaster.ddm@gmail.com>

taylor_socfortress committed May 18, 2024 at 16:23 UTC 33bc81131a2348db4e465282bf5d7994e3bc3b75
17 files changed +733 -252
backend/alembic/versions/39c3aaec0084_new_agents_table_schema.py new
+35
@@ -0,0 +1,35 @@
1 +"""New agents table schema
2 +
3 +Revision ID: 39c3aaec0084
4 +Revises: ec63589cc24d
5 +Create Date: 2024-05-17 17:17:39.140779
6 +
7 +"""
8 +from typing import Sequence
9 +from typing import Union
10 +
11 +from sqlalchemy.dialects import mysql
12 +
13 +from alembic import op
14 +
15 +# revision identifiers, used by Alembic.
16 +revision: str = "39c3aaec0084"
17 +down_revision: Union[str, None] = "ec63589cc24d"
18 +branch_labels: Union[str, Sequence[str], None] = None
19 +depends_on: Union[str, Sequence[str], None] = None
20 +
21 +
22 +def upgrade() -> None:
23 + # ### commands auto generated by Alembic - please adjust! ###
24 + op.alter_column("agents", "velociraptor_id", existing_type=mysql.VARCHAR(length=256), nullable=True)
25 + op.alter_column("agents", "velociraptor_last_seen", existing_type=mysql.DATETIME(), nullable=True)
26 + op.alter_column("agents", "velociraptor_agent_version", existing_type=mysql.VARCHAR(length=256), nullable=True)
27 + # ### end Alembic commands ###
28 +
29 +
30 +def downgrade() -> None:
31 + # ### commands auto generated by Alembic - please adjust! ###
32 + op.alter_column("agents", "velociraptor_agent_version", existing_type=mysql.VARCHAR(length=256), nullable=False)
33 + op.alter_column("agents", "velociraptor_last_seen", existing_type=mysql.DATETIME(), nullable=False)
34 + op.alter_column("agents", "velociraptor_id", existing_type=mysql.VARCHAR(length=256), nullable=False)
35 + # ### end Alembic commands ###
backend/app/agents/routes/agents.py
+51 -9
@@ -18,7 +18,8 @@ from app.agents.schema.agents import OutdatedWazuhAgentsResponse
18 from app.agents.schema.agents import SyncedAgentsResponse
19 from app.agents.services.status import get_outdated_agents_velociraptor
20 from app.agents.services.status import get_outdated_agents_wazuh
21 -from app.agents.services.sync import sync_agents
21 +from app.agents.services.sync import sync_agents_velociraptor
22 +from app.agents.services.sync import sync_agents_wazuh
23 from app.agents.velociraptor.services.agents import delete_agent_velociraptor
24 from app.agents.wazuh.schema.agents import WazuhAgentScaPolicyResultsResponse
25 from app.agents.wazuh.schema.agents import WazuhAgentScaResponse
@@ -223,10 +224,7 @@ async def get_agent_by_hostname(
224 Security(AuthHandler().require_any_scope("admin", "analyst", "scheduler")),
225 ],
226 )
226 -async def sync_all_agents(
227 - # backgroud_tasks: BackgroundTasks,
228 - session: AsyncSession = Depends(get_db),
229 -) -> SyncedAgentsResponse:
227 +async def sync_all_agents() -> SyncedAgentsResponse:
228 """
229 Sync all agents from Wazuh Manager.
230
@@ -241,10 +239,10 @@ async def sync_all_agents(
239 - SyncedAgentsResponse: The response model indicating the success of the sync operation.
240
241 """
244 - logger.info("Syncing agents from Wazuh Manager")
245 - # backgroud_tasks.add_task(sync_agents, session)
242 + logger.info("Syncing agents as part of scheduled job")
243 loop = asyncio.get_event_loop()
247 - loop.create_task(sync_agents(session=session))
244 + await loop.create_task(sync_agents_wazuh())
245 + await loop.create_task(sync_agents_velociraptor())
246 return SyncedAgentsResponse(
247 success=True,
248 message="Agents synced started successfully",
@@ -474,7 +472,51 @@ async def get_outdated_velociraptor_agents(
472 return await get_outdated_agents_velociraptor(session)
473
474
477 -# ! TODO: FINISH THIS
475 +@agents_router.put(
476 + "/{agent_id}/update",
477 + response_model=AgentModifyResponse,
478 + description="Update agent",
479 + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))],
480 +)
481 +async def update_agent(
482 + agent_id: str,
483 + velociraptor_id: str,
484 + session: AsyncSession = Depends(get_db),
485 +) -> AgentModifyResponse:
486 + """
487 + Updates an agent's velociraptor_id
488 +
489 + Args:
490 + agent_id (str): The ID of the agent to be updated.
491 + velociraptor_id (str): The new velociraptor_id of the agent.
492 + session (AsyncSession, optional): The database session. Defaults to Depends(get_db).
493 +
494 + Returns:
495 + AgentModifyResponse: The response indicating the success or failure of the update.
496 + """
497 + logger.info(f"Updating agent {agent_id} with Velociraptor ID: {velociraptor_id}")
498 + try:
499 + result = await session.execute(select(Agents).filter(Agents.agent_id == agent_id))
500 + agent = result.scalars().first()
501 + if not agent:
502 + raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
503 + agent.velociraptor_id = velociraptor_id
504 + await session.commit()
505 + logger.info(f"Agent {agent_id} updated with Velociraptor ID: {velociraptor_id}")
506 + return AgentModifyResponse(
507 + success=True,
508 + message=f"Agent {agent_id} updated with Velociraptor ID: {velociraptor_id}",
509 + )
510 + except Exception as e:
511 + if not agent:
512 + raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found")
513 + logger.error(f"Failed to update agent {agent_id} with Velociraptor ID: {velociraptor_id}: {e}")
514 + raise HTTPException(
515 + status_code=500,
516 + detail=f"Failed to update agent {agent_id} with Velociraptor ID: {velociraptor_id}: {e}",
517 + )
518 +
519 +
520 @agents_router.delete(
521 "/{agent_id}/delete",
522 response_model=AgentModifyResponse,
backend/app/agents/schema/agents.py
+4
@@ -18,6 +18,10 @@ class SyncedAgent(WazuhAgent, VelociraptorAgent):
18 pass
19
20
21 +class SyncedWazuhAgent(WazuhAgent):
22 + pass
23 +
24 +
25 class SyncedAgentsResponse(BaseModel):
26 # agents_added: List[SyncedAgent]
27 success: bool
backend/app/agents/services/sync.py
+171 -61
@@ -1,3 +1,5 @@
1 +from datetime import datetime
2 +from datetime import timezone
3 from typing import List
4
5 from fastapi import HTTPException
@@ -7,12 +9,14 @@ from sqlalchemy.future import select
9
10 import app.agents.velociraptor.services.agents as velociraptor_services
11 import app.agents.wazuh.services.agents as wazuh_services
10 -from app.agents.schema.agents import SyncedAgent
12 from app.agents.schema.agents import SyncedAgentsResponse
13 +from app.agents.schema.agents import SyncedWazuhAgent
14 from app.agents.velociraptor.schema.agents import VelociraptorAgent
15 +from app.agents.velociraptor.schema.agents import VelociraptorClients
16 from app.agents.wazuh.schema.agents import WazuhAgent
17 from app.agents.wazuh.schema.agents import WazuhAgentsList
18 from app.connectors.models import Connectors
19 +from app.db.db_session import get_db_session
20 from app.db.universal_models import Agents
21
22
@@ -35,6 +39,22 @@ async def fetch_wazuh_agents() -> WazuhAgentsList:
39 )
40
41
42 +async def fetch_velociraptor_clients() -> VelociraptorClients:
43 + """
44 + Fetches clients from Velociraptor service.
45 +
46 + Args:
47 + None
48 +
49 + Returns:
50 + VelociraptorClientsList: The fetched clients.
51 + """
52 + collected_velociraptor_agents = await velociraptor_services.collect_velociraptor_clients()
53 + return VelociraptorClients(
54 + clients=collected_velociraptor_agents,
55 + )
56 +
57 +
58 async def fetch_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
59 """
60 Fetches agent details from Velociraptor service.
@@ -48,10 +68,22 @@ async def fetch_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
68 return await velociraptor_services.collect_velociraptor_agent(agent_name)
69
70
51 -async def add_agent_to_db(
71 +async def fetch_velociraptor_agent_via_client_id(client_id: str) -> VelociraptorAgent:
72 + """
73 + Fetches agent details from Velociraptor service.
74 +
75 + Args:
76 + client_id (str): The client_id of the agent to fetch.
77 +
78 + Returns:
79 + VelociraptorAgent: The fetched agent details.
80 + """
81 + return await velociraptor_services.collect_velociraptor_agent_via_client_id(client_id)
82 +
83 +
84 +async def add_wazuh_agent_in_db(
85 session: AsyncSession,
86 agent: WazuhAgent,
54 - client: VelociraptorAgent,
87 customer_code: str,
88 ):
89 """Add new agent to database.
@@ -59,14 +91,13 @@ async def add_agent_to_db(
91 Args:
92 session (AsyncSession): The asynchronous session object for database operations.
93 agent (WazuhAgent): The Wazuh agent object to be added.
62 - client (VelociraptorAgent): The Velociraptor agent object associated with the Wazuh agent.
94 customer_code (str): The customer code for the agent.
95
96 Returns:
97 None
98
99 """
69 - new_agent = Agents.create_from_model(agent, client, customer_code)
100 + new_agent = Agents.create_wazuh_agent_from_model(agent, customer_code)
101 session.add(new_agent)
102 logger.info(f"Adding agent {agent.agent_name} to the database")
103 try:
@@ -78,11 +109,10 @@ async def add_agent_to_db(
109 logger.info(f"Agent {agent.agent_name} added to the database")
110
111
81 -async def update_agent_in_db(
112 +async def update_wazuh_agent_in_db(
113 session: AsyncSession,
114 existing_agent: Agents,
115 agent: WazuhAgent,
85 - client: VelociraptorAgent,
116 customer_code: str,
117 ):
118 """Update existing agent in database.
@@ -91,14 +121,13 @@ async def update_agent_in_db(
121 session (AsyncSession): The async session object for database operations.
122 existing_agent (Agents): The existing agent object in the database.
123 agent (WazuhAgent): The updated agent object.
94 - client (VelociraptorAgent): The updated client object.
124 customer_code (str): The customer code associated with the agent.
125
126 Returns:
127 None
128
129 """
101 - existing_agent.update_from_model(agent, client, customer_code)
130 + existing_agent.update_wazuh_agent_from_model(agent, customer_code)
131 await session.commit() # Use the await keyword to commit asynchronously
132 logger.info(f"Agent {agent.agent_name} updated in the database")
133
@@ -150,7 +179,24 @@ async def get_velociraptor_agent(agent_name):
179 return None
180
181
153 -async def process_velociraptor_agent(session, wazuh_agent):
182 +async def get_velociraptor_agent_by_client_id(client_id):
183 + """
184 + Retrieves a Velociraptor agent with the specified client_id.
185 +
186 + Args:
187 + client_id (str): The client_id of the agent to retrieve.
188 +
189 + Returns:
190 + VelociraptorAgent: The retrieved Velociraptor agent, or None if retrieval fails.
191 + """
192 + try:
193 + return await fetch_velociraptor_agent_via_client_id(client_id)
194 + except Exception as e:
195 + logger.error(f"Failed to collect Velociraptor Agent for {client_id}: {e}")
196 + return None
197 +
198 +
199 +async def process_velociraptor_agent(session, agent, client_id=None):
200 """
201 Process the Velociraptor agent for a given Wazuh agent.
202
@@ -164,7 +210,9 @@ async def process_velociraptor_agent(session, wazuh_agent):
210 try:
211 velociraptor_connector = await get_velociraptor_connector(session)
212 if velociraptor_connector.connector_verified:
167 - velociraptor_agent = await get_velociraptor_agent(wazuh_agent.agent_name)
213 + velociraptor_agent = await get_velociraptor_agent(agent)
214 + if client_id is not None:
215 + velociraptor_agent = await get_velociraptor_agent_by_client_id(client_id)
216 else:
217 velociraptor_agent = VelociraptorAgent(
218 client_id="Unknown",
@@ -173,67 +221,129 @@ async def process_velociraptor_agent(session, wazuh_agent):
221 )
222 return velociraptor_agent
223 except Exception as e:
176 - logger.error(f"Failed to process agent {wazuh_agent.agent_name}: {e}")
224 + logger.error(f"Failed to process agent {agent}: {e}")
225 return None
226
227
180 -async def sync_agents(session: AsyncSession) -> SyncedAgentsResponse:
181 - """
182 - Synchronize agents from Wazuh and Velociraptor services.
183 -
184 - This function fetches the list of Wazuh agents, collects the corresponding Velociraptor agent for each Wazuh agent,
185 - and synchronizes the agents in the database. It returns a response indicating the success of the synchronization
186 - operation and the list of agents that were added.
187 -
188 - :param session: The database session to use for querying and updating agents.
189 - :type session: AsyncSession
190 - :return: The response indicating the success of the synchronization operation and the list of agents added.
191 - :rtype: SyncedAgentsResponse
192 - """
228 +async def sync_agents_wazuh() -> SyncedAgentsResponse:
229 wazuh_agents_list = await fetch_wazuh_agents()
230 logger.info(f"Collected Wazuh Agents: {wazuh_agents_list}")
231
232 agents_added_list: List[WazuhAgent] = []
233
198 - for wazuh_agent in wazuh_agents_list.agents:
199 - logger.info(f"Collecting Velociraptor Agent for {wazuh_agent.agent_name}")
234 + async with get_db_session() as session: # Create a new session here
235 + for wazuh_agent in wazuh_agents_list.agents:
236 + customer_code = extract_customer_code(wazuh_agent.agent_label)
237
201 - try:
202 - velociraptor_agent = await process_velociraptor_agent(session, wazuh_agent)
203 - except Exception as e:
204 - logger.error(
205 - f"Failed to collect Velociraptor Agent for {wazuh_agent.agent_name}: {e}",
206 - )
207 - continue
208 -
209 - customer_code = extract_customer_code(wazuh_agent.agent_label)
210 -
211 - # Asynchronously fetch the existing agent
212 - existing_agent_query = select(Agents).filter(
213 - Agents.hostname == wazuh_agent.agent_name,
214 - )
215 - result = await session.execute(existing_agent_query)
216 - existing_agent = result.scalars().first()
217 -
218 - if existing_agent:
219 - await update_agent_in_db(
220 - session,
221 - existing_agent,
222 - wazuh_agent,
223 - velociraptor_agent,
224 - customer_code,
225 - )
226 - else:
227 - await add_agent_to_db(
228 - session,
229 - wazuh_agent,
230 - velociraptor_agent,
231 - customer_code,
238 + existing_agent_query = select(Agents).filter(
239 + Agents.hostname == wazuh_agent.agent_name,
240 )
241 + result = await session.execute(existing_agent_query)
242 + existing_agent = result.scalars().first()
243 +
244 + if existing_agent:
245 + await update_wazuh_agent_in_db(session, existing_agent, wazuh_agent, customer_code)
246 + else:
247 + await add_wazuh_agent_in_db(session, wazuh_agent, customer_code)
248 +
249 + synced_wazuh_agent = SyncedWazuhAgent(**wazuh_agent.dict())
250 + agents_added_list.append(synced_wazuh_agent)
251 +
252 + logger.info(f"Agents Added List: {agents_added_list}")
253 +
254 + # Close the session
255 + await session.close()
256 +
257 + return SyncedAgentsResponse(
258 + success=True,
259 + message="Agents synced successfully",
260 + )
261 +
262 +
263 +async def update_agent_with_velociraptor_in_db(
264 + session: AsyncSession,
265 + agent: Agents,
266 + velociraptor_agent: VelociraptorAgent,
267 +):
268 + """Update existing agent in database with Velociraptor details.
269 +
270 + Args:
271 + session (AsyncSession): The async session object for database operations.
272 + agent (Agents): The existing agent object in the database.
273 + client (VelociraptorAgent): The updated client object.
274
234 - # Combine the wazuh agent and velociraptor agent into one object
235 - synced_agent = SyncedAgent(**wazuh_agent.dict(), **velociraptor_agent.dict())
236 - agents_added_list.append(synced_agent)
275 + Returns:
276 + None
277 +
278 + """
279 + logger.info(f"Updating agent {agent.hostname} with Velociraptor details in the database")
280 + agent.update_velociraptor_details(velociraptor_agent)
281 + session.add(agent) # Add the updated agent back to the session
282 + await session.commit() # Use the await keyword to commit asynchronously
283 + logger.info("Agent updated with Velociraptor details in the database")
284 +
285 +
286 +async def sync_agents_velociraptor() -> SyncedAgentsResponse:
287 + """
288 + Syncronizes the agents with Velociraptor. This function retrieves all the
289 + agents from the `Agents` table and invokes the velociraptor API with the
290 + hostname. If the hostname cannot be found within Velociraptor, and the agent's
291 + `velociraptor_id` is not None, invoke the Velociraptor API and pass it the
292 + `velociraptor_id`.
293 +
294 + :param session: The database session to use for querying and updating agents.
295 + :type session: AsyncSession
296 + :return: The response indicating the success of the synchronization operation and the list of agents added.
297 + :rtype: SyncedAgentsResponse
298 + """
299 + agents_added_list: List[VelociraptorAgent] = []
300 +
301 + velociraptor_clients = await fetch_velociraptor_clients()
302 + velociraptor_clients = velociraptor_clients.clients if hasattr(velociraptor_clients, "clients") else []
303 +
304 + async with get_db_session() as session: # Create a new session here
305 + existing_agents_query = select(Agents)
306 + result = await session.execute(existing_agents_query)
307 + existing_agents = result.scalars().all()
308 +
309 + for agent in existing_agents:
310 + logger.info(f"Collecting Velociraptor Agent for {agent.hostname}")
311 +
312 + try:
313 + # Build the velociraptor_agent where the hostname or `client_id` is that equal to the `agents`
314 + velociraptor_agent = next(
315 + (
316 + client
317 + for client in velociraptor_clients
318 + if client.os_info.hostname == agent.hostname or client.client_id == agent.velociraptor_id
319 + ),
320 + None,
321 + )
322 + # Convert Unix epoch timestamp to datetime
323 + last_seen_at = datetime.fromtimestamp(
324 + int(velociraptor_agent.last_seen_at) / 1e6,
325 + ) # Divide by 1e6 to convert from microseconds to seconds
326 + # Convert datetime to ISO 8601 format without fractional seconds
327 + last_seen_at_iso = last_seen_at.replace(tzinfo=timezone.utc).isoformat(timespec="seconds")
328 + velociraptor_agent = VelociraptorAgent(
329 + velociraptor_id=velociraptor_agent.client_id,
330 + velociraptor_last_seen=last_seen_at_iso,
331 + velociraptor_agent_version=velociraptor_agent.agent_information.version,
332 + )
333 +
334 + except Exception as e:
335 + logger.error(
336 + f"Failed to collect Velociraptor Agent for {agent.hostname}: {e}",
337 + )
338 + continue
339 +
340 + if velociraptor_agent:
341 + # Update the agent with the Velociraptor client's details
342 + await update_agent_with_velociraptor_in_db(session, agent, velociraptor_agent)
343 + agents_added_list.append(velociraptor_agent)
344 +
345 + # Close the session
346 + await session.close()
347
348 logger.info(f"Agents Added List: {agents_added_list}")
349 return SyncedAgentsResponse(
backend/app/agents/velociraptor/schema/agents.py
+36
@@ -1,4 +1,5 @@
1 from datetime import datetime
2 +from typing import List
3 from typing import Optional
4
5 from pydantic import BaseModel
@@ -17,3 +18,38 @@ class VelociraptorAgent(BaseModel):
18
19 class Config:
20 allow_population_by_field_name = True
21 +
22 +
23 +class VelociraptorAgentInformation(BaseModel):
24 + version: str
25 + name: str
26 + build_time: str
27 + build_url: str
28 +
29 +
30 +class VelociraptorOSInfo(BaseModel):
31 + system: str
32 + hostname: str
33 + release: str
34 + machine: str
35 + fqdn: str
36 + mac_addresses: List[str]
37 +
38 +
39 +class VelociraptorClient(BaseModel):
40 + client_id: str
41 + agent_information: VelociraptorAgentInformation
42 + os_info: VelociraptorOSInfo
43 + first_seen_at: int
44 + last_seen_at: int
45 + last_ip: str
46 + last_interrogate_flow_id: str
47 + last_interrogate_artifact_name: str
48 + labels: List[str]
49 + last_hunt_timestamp: int
50 + last_event_table_version: int
51 + last_label_timestamp: int
52 +
53 +
54 +class VelociraptorClients(BaseModel):
55 + clients: List[VelociraptorClient]
backend/app/agents/velociraptor/services/agents.py
+72
@@ -21,6 +21,21 @@ def create_query(query: str) -> str:
21 return query
22
23
24 +async def collect_velociraptor_clients() -> list:
25 + """
26 + Collects all clients from Velociraptor.
27 +
28 + Returns:
29 + list: A list of all clients.
30 + """
31 + velociraptor_service = await UniversalService.create("Velociraptor")
32 + query = create_query(
33 + "SELECT * FROM clients()",
34 + )
35 + flow = velociraptor_service.execute_query(query)
36 + return flow["results"]
37 +
38 +
39 async def collect_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
40 """
41 Retrieves the client ID, last_seen_at and client version based on the agent name from Velociraptor.
@@ -78,6 +93,63 @@ async def collect_velociraptor_agent(agent_name: str) -> VelociraptorAgent:
93 )
94
95
96 +async def collect_velociraptor_agent_via_client_id(client_id: str) -> VelociraptorAgent:
97 + """
98 + Retrieves the client ID, last_seen_at and client version based on the agent name from Velociraptor.
99 +
100 + Args:
101 + agent_name (str): The name of the agent.
102 +
103 + Returns:
104 + str: The client ID if found, None otherwise.
105 + str: The last seen at timestamp if found, Default timsetamp otherwise.
106 + """
107 + logger.info(f"Collecting agent {client_id} from Velociraptor")
108 + velociraptor_service = await UniversalService.create("Velociraptor")
109 + try:
110 + client_id = await velociraptor_service.get_client_id_via_client_id(client_id)
111 + client_id = client_id["results"][0]["client_id"]
112 + except (KeyError, IndexError, TypeError) as e:
113 + logger.error(f"Failed to get client ID for {client_id}. Error: {e}")
114 + return VelociraptorAgent(
115 + client_id="Unknown",
116 + client_last_seen="Unknown",
117 + client_version="Unknown",
118 + )
119 +
120 + try:
121 + vql_last_seen_at = f"select last_seen_at from clients(search='host:{client_id}')"
122 + last_seen_at = await velociraptor_service._get_last_seen_timestamp(
123 + vql_last_seen_at,
124 + )
125 + client_last_seen = datetime.fromtimestamp(
126 + int(last_seen_at) / 1000000,
127 + ).strftime(
128 + "%Y-%m-%dT%H:%M:%S+00:00",
129 + ) # Converting to string format
130 + except Exception as e:
131 + logger.error(
132 + f"Failed to get or convert last seen at for {client_id}. Error: {e}",
133 + )
134 + client_last_seen = "1970-01-01T00:00:00+00:00"
135 +
136 + try:
137 + vql_client_version = f"select * from clients(search='host:{client_id}')"
138 + # client_version = UniversalService()._get_client_version(vql_client_version)
139 + client_version = await velociraptor_service._get_client_version(
140 + vql_client_version,
141 + )
142 + except Exception as e:
143 + logger.error(f"Failed to get client version for {client_id}. Error: {e}")
144 + client_version = "Unknown"
145 +
146 + return VelociraptorAgent(
147 + client_id=client_id,
148 + client_last_seen=client_last_seen,
149 + client_version=client_version,
150 + )
151 +
152 +
153 def execute_query(universal_service, query: str) -> dict:
154 """
155 Executes a query using the provided universal service.
backend/app/connectors/velociraptor/utils/universal.py
+34
@@ -270,6 +270,40 @@ class UniversalService:
270 "results": [{"client_id": None}],
271 }
272
273 + async def get_client_id_via_client_id(self, client_id: str):
274 + """
275 + Get the client_id associated with a given client_id.
276 +
277 + Args:
278 + client_id (str): The client_id to search for.
279 +
280 + Returns:
281 + dict: A dictionary with the success status, a message, and potentially the client_id.
282 + """
283 + # Formulate queries
284 + try:
285 + vql_client_id = f"select client_id,os_info from clients(search='client_id:{client_id}')"
286 + vql_last_seen_at = f"select last_seen_at from clients(search='client_id:{client_id}')"
287 +
288 + # Get the last seen timestamp
289 + logger.info(f"Getting last seen at timestamp for {client_id}")
290 +
291 + last_seen_at = await self._get_last_seen_timestamp(vql_last_seen_at)
292 +
293 + logger.info(f"Last seen at timestamp for {client_id}: {last_seen_at}")
294 +
295 + # if last_seen_at is longer than 30 seconds from now, return False
296 + if await self._is_offline(last_seen_at):
297 + return self.execute_query(vql_client_id)
298 +
299 + return self.execute_query(vql_client_id)
300 + except Exception as e:
301 + return {
302 + "success": False,
303 + "message": f"Failed to get Client ID for {client_id}: {e}",
304 + "results": [{"client_id": None}],
305 + }
306 +
307 async def _get_last_seen_timestamp(self, vql: str):
308 """
309 Executes the VQL query and returns the last_seen_at timestamp.
backend/app/db/universal_models.py
+70 -10
@@ -1,6 +1,7 @@
1 from datetime import datetime
2 from typing import Optional
3
4 +from loguru import logger
5 from sqlalchemy import Column
6 from sqlalchemy import Float
7 from sqlalchemy import LargeBinary
@@ -93,11 +94,11 @@ class Agents(SQLModel, table=True):
94 label: str = Field(max_length=256)
95 critical_asset: bool = Field(default=False)
96 wazuh_last_seen: datetime
96 - velociraptor_id: str = Field(max_length=256)
97 - velociraptor_last_seen: datetime
97 + velociraptor_id: Optional[str] = Field(max_length=256)
98 + velociraptor_last_seen: Optional[datetime]
99 wazuh_agent_version: str = Field(max_length=256)
100 wazuh_agent_status: str = Field("not found", max_length=256)
100 - velociraptor_agent_version: str = Field(max_length=256)
101 + velociraptor_agent_version: Optional[str] = Field(max_length=256)
102 customer_code: Optional[str] = Field(foreign_key="customers.customer_code", max_length=256)
103 quarantined: bool = Field(default=False)
104
@@ -120,11 +121,32 @@ class Agents(SQLModel, table=True):
121 wazuh_last_seen=wazuh_last_seen_value,
122 wazuh_agent_version=wazuh_agent.wazuh_agent_version,
123 wazuh_agent_status=wazuh_agent.wazuh_agent_status if wazuh_agent.wazuh_agent_status else "not found",
123 - velociraptor_id=velociraptor_agent.client_id if velociraptor_agent.client_id else "n/a",
124 + velociraptor_id=velociraptor_agent.client_id if velociraptor_agent and velociraptor_agent.client_id else None,
125 velociraptor_last_seen=velociraptor_agent.client_last_seen_as_datetime
125 - if velociraptor_agent.client_last_seen_as_datetime
126 - else "1970-01-01T00:00:00+00:00",
127 - velociraptor_agent_version=velociraptor_agent.client_version if velociraptor_agent.client_version else "n/a",
126 + if velociraptor_agent and velociraptor_agent.client_last_seen_as_datetime
127 + else None,
128 + velociraptor_agent_version=velociraptor_agent.client_version
129 + if velociraptor_agent and velociraptor_agent.client_version
130 + else None,
131 + customer_code=customer_code,
132 + )
133 +
134 + @classmethod
135 + def create_wazuh_agent_from_model(cls, wazuh_agent, customer_code):
136 + if wazuh_agent.agent_last_seen == "Unknown":
137 + wazuh_last_seen_value = "1970-01-01T00:00:00+00:00"
138 + else:
139 + wazuh_last_seen_value = wazuh_agent.agent_last_seen_as_datetime
140 +
141 + return cls(
142 + agent_id=wazuh_agent.agent_id,
143 + hostname=wazuh_agent.agent_name,
144 + ip_address=wazuh_agent.agent_ip,
145 + os=wazuh_agent.agent_os,
146 + label=wazuh_agent.agent_label,
147 + wazuh_last_seen=wazuh_last_seen_value,
148 + wazuh_agent_version=wazuh_agent.wazuh_agent_version,
149 + wazuh_agent_status=wazuh_agent.wazuh_agent_status if wazuh_agent.wazuh_agent_status else "not found",
150 customer_code=customer_code,
151 )
152
@@ -145,11 +167,49 @@ class Agents(SQLModel, table=True):
167 self.wazuh_last_seen = wazuh_last_seen_value
168 self.wazuh_agent_version = wazuh_agent.wazuh_agent_version
169 self.wazuh_agent_status = wazuh_agent.wazuh_agent_status if wazuh_agent.wazuh_agent_status else "not found"
148 - self.velociraptor_id = velociraptor_agent.client_id if velociraptor_agent.client_id else "n/a"
149 - self.velociraptor_last_seen = velociraptor_agent.client_last_seen_as_datetime
150 - self.velociraptor_agent_version = velociraptor_agent.client_version
170 + self.velociraptor_id = velociraptor_agent.client_id if velociraptor_agent and velociraptor_agent.client_id else None
171 + self.velociraptor_last_seen = (
172 + velociraptor_agent.client_last_seen_as_datetime
173 + if velociraptor_agent and velociraptor_agent.client_last_seen_as_datetime
174 + else None
175 + )
176 + self.velociraptor_agent_version = (
177 + velociraptor_agent.client_version if velociraptor_agent and velociraptor_agent.client_version else None
178 + )
179 self.customer_code = customer_code
180
181 + def update_wazuh_agent_from_model(self, wazuh_agent, customer_code):
182 + if wazuh_agent.agent_last_seen == "Unknown" or wazuh_agent.agent_last_seen == "1970-01-01T00:00:00+00:00":
183 + wazuh_last_seen_value = datetime.strptime(
184 + "1970-01-01T00:00:00+00:00",
185 + "%Y-%m-%dT%H:%M:%S%z",
186 + )
187 + else:
188 + wazuh_last_seen_value = wazuh_agent.agent_last_seen_as_datetime
189 +
190 + self.agent_id = wazuh_agent.agent_id
191 + self.hostname = wazuh_agent.agent_name
192 + self.ip_address = wazuh_agent.agent_ip
193 + self.os = wazuh_agent.agent_os
194 + self.label = wazuh_agent.agent_label
195 + self.wazuh_last_seen = wazuh_last_seen_value
196 + self.wazuh_agent_version = wazuh_agent.wazuh_agent_version
197 + self.wazuh_agent_status = wazuh_agent.wazuh_agent_status if wazuh_agent.wazuh_agent_status else "not found"
198 + self.customer_code = customer_code
199 +
200 + def update_velociraptor_details(self, velociraptor_agent):
201 + logger.info(f"Updating Velociraptor details for agent {self}")
202 + self.velociraptor_id = velociraptor_agent.client_id if velociraptor_agent and velociraptor_agent.client_id else None
203 + self.velociraptor_last_seen = (
204 + velociraptor_agent.client_last_seen_as_datetime
205 + if velociraptor_agent and velociraptor_agent.client_last_seen_as_datetime
206 + else None
207 + )
208 + self.velociraptor_agent_version = (
209 + velociraptor_agent.client_version if velociraptor_agent and velociraptor_agent.client_version else None
210 + )
211 + logger.info(f"Updated with Velociraptor details: {self}")
212 +
213
214 class LogEntry(SQLModel, table=True):
215 __tablename__ = "log_entries"
backend/app/schedulers/services/agent_sync.py
+1 -1
@@ -23,7 +23,7 @@ async def agent_sync():
23 """
24 logger.info("Synchronizing agents via scheduler...")
25 async with get_db_session() as session:
26 - await sync_all_agents(session=session)
26 + await sync_all_agents()
27
28 stmt = select(JobMetadata).where(JobMetadata.job_id == "agent_sync")
29 result = await session.execute(stmt)
frontend/package-lock.json
+107 -130
@@ -28,7 +28,7 @@
28 "jose": "^5.3.0",
29 "js-md5": "^0.8.3",
30 "lodash": "^4.17.21",
31 - "markdown-it-highlightjs": "^4.0.1",
31 + "markdown-it-highlightjs": "^4.1.0",
32 "mitt": "^3.0.1",
33 "naive-ui": "^2.38.2",
34 "password-validator": "^5.3.0",
@@ -49,7 +49,7 @@
49 "devDependencies": {
50 "@clack/prompts": "^0.7.0",
51 "@iconify/vue": "^4.1.2",
52 - "@rushstack/eslint-patch": "^1.10.2",
52 + "@rushstack/eslint-patch": "^1.10.3",
53 "@tsconfig/node18": "^18.2.4",
54 "@types/bytes": "^3.1.4",
55 "@types/file-saver": "^2.0.7",
@@ -58,7 +58,7 @@
58 "@types/html2canvas": "^1.0.0",
59 "@types/inquirer": "^9.0.7",
60 "@types/jsdom": "^21.1.6",
61 - "@types/lodash": "^4.17.3",
61 + "@types/lodash": "^4.17.4",
62 "@types/markdown-it": "^14.1.1",
63 "@types/markdown-it-highlightjs": "^3.3.4",
64 "@types/node": "^20.12.12",
@@ -83,7 +83,7 @@
83 "picocolors": "^1.0.1",
84 "postcss": "^8.4.38",
85 "prettier": "^3.2.5",
86 - "sass": "^1.77.1",
86 + "sass": "^1.77.2",
87 "shiki": "^1.5.2",
88 "start-server-and-test": "^2.0.3",
89 "tailwind-config-viewer": "^2.0.2",
@@ -150,9 +150,9 @@
150 }
151 },
152 "node_modules/@antfu/utils": {
153 - "version": "0.7.7",
154 - "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.7.tgz",
155 - "integrity": "sha512-gFPqTG7otEJ8uP6wrhDv6mqwGWYZKNvAcCq6u9hOj0c+IKCEsY4L1oC9trPq2SaWIzAfHvqfBDxF591JkMf+kg==",
153 + "version": "0.7.8",
154 + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.8.tgz",
155 + "integrity": "sha512-rWQkqXRESdjXtc+7NRfK9lASQjpXJu1ayp7qi1d23zZorY+wBHVLHHoVcMsEnkqEBWTFqbztO7/QdJFzyEcLTg==",
156 "funding": {
157 "url": "https://github.com/sponsors/antfu"
158 }
@@ -664,6 +664,7 @@
664 },
665 "node_modules/@clack/prompts/node_modules/is-unicode-supported": {
666 "version": "1.3.0",
667 + "dev": true,
668 "inBundle": true,
669 "license": "MIT",
670 "engines": {
@@ -1837,9 +1838,9 @@
1838 ]
1839 },
1840 "node_modules/@rushstack/eslint-patch": {
1840 - "version": "1.10.2",
1841 - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.2.tgz",
1842 - "integrity": "sha512-hw437iINopmQuxWPSUEvqE56NCPsiU8N4AYtfHmJFckclktzK9YQJieD3XkDCDH4OjL+C7zgPUh73R/nrcHrqw==",
1841 + "version": "1.10.3",
1842 + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz",
1843 + "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==",
1844 "dev": true
1845 },
1846 "node_modules/@shikijs/core": {
@@ -1985,9 +1986,9 @@
1986 "dev": true
1987 },
1988 "node_modules/@types/lodash": {
1988 - "version": "4.17.3",
1989 - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.3.tgz",
1990 - "integrity": "sha512-zmNrEJaBvNskZXQWaUQq6bktF4IDGVfDS78M+YEk5aCn9M/b94/mB/6WCyfH2/MjwBdc6QuOor95CIlKWYRL3A=="
1989 + "version": "4.17.4",
1990 + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.4.tgz",
1991 + "integrity": "sha512-wYCP26ZLxaT3R39kiN2+HcJ4kTd3U1waI/cY7ivWYqFP6pW3ZNpvi6Wd6PHZx7T/t8z0vlkXMg3QYLa7DZ/IJQ=="
1992 },
1993 "node_modules/@types/lodash-es": {
1994 "version": "4.17.12",
@@ -2663,21 +2664,19 @@
2664 }
2665 },
2666 "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/eslint-plugin": {
2666 - "version": "7.8.0",
2667 - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz",
2668 - "integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==",
2667 + "version": "7.9.0",
2668 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.9.0.tgz",
2669 + "integrity": "sha512-6e+X0X3sFe/G/54aC3jt0txuMTURqLyekmEHViqyA2VnxhLMpvA6nqmcjIy+Cr9tLDHPssA74BP5Mx9HQIxBEA==",
2670 "dev": true,
2671 "dependencies": {
2672 "@eslint-community/regexpp": "^4.10.0",
2672 - "@typescript-eslint/scope-manager": "7.8.0",
2673 - "@typescript-eslint/type-utils": "7.8.0",
2674 - "@typescript-eslint/utils": "7.8.0",
2675 - "@typescript-eslint/visitor-keys": "7.8.0",
2676 - "debug": "^4.3.4",
2673 + "@typescript-eslint/scope-manager": "7.9.0",
2674 + "@typescript-eslint/type-utils": "7.9.0",
2675 + "@typescript-eslint/utils": "7.9.0",
2676 + "@typescript-eslint/visitor-keys": "7.9.0",
2677 "graphemer": "^1.4.0",
2678 "ignore": "^5.3.1",
2679 "natural-compare": "^1.4.0",
2680 - "semver": "^7.6.0",
2680 "ts-api-utils": "^1.3.0"
2681 },
2682 "engines": {
@@ -2698,15 +2697,15 @@
2697 }
2698 },
2699 "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/parser": {
2701 - "version": "7.8.0",
2702 - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz",
2703 - "integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==",
2700 + "version": "7.9.0",
2701 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.9.0.tgz",
2702 + "integrity": "sha512-qHMJfkL5qvgQB2aLvhUSXxbK7OLnDkwPzFalg458pxQgfxKDfT1ZDbHQM/I6mDIf/svlMkj21kzKuQ2ixJlatQ==",
2703 "dev": true,
2704 "dependencies": {
2706 - "@typescript-eslint/scope-manager": "7.8.0",
2707 - "@typescript-eslint/types": "7.8.0",
2708 - "@typescript-eslint/typescript-estree": "7.8.0",
2709 - "@typescript-eslint/visitor-keys": "7.8.0",
2705 + "@typescript-eslint/scope-manager": "7.9.0",
2706 + "@typescript-eslint/types": "7.9.0",
2707 + "@typescript-eslint/typescript-estree": "7.9.0",
2708 + "@typescript-eslint/visitor-keys": "7.9.0",
2709 "debug": "^4.3.4"
2710 },
2711 "engines": {
@@ -2726,13 +2725,13 @@
2725 }
2726 },
2727 "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/scope-manager": {
2729 - "version": "7.8.0",
2730 - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz",
2731 - "integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==",
2728 + "version": "7.9.0",
2729 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.9.0.tgz",
2730 + "integrity": "sha512-ZwPK4DeCDxr3GJltRz5iZejPFAAr4Wk3+2WIBaj1L5PYK5RgxExu/Y68FFVclN0y6GGwH8q+KgKRCvaTmFBbgQ==",
2731 "dev": true,
2732 "dependencies": {
2734 - "@typescript-eslint/types": "7.8.0",
2735 - "@typescript-eslint/visitor-keys": "7.8.0"
2733 + "@typescript-eslint/types": "7.9.0",
2734 + "@typescript-eslint/visitor-keys": "7.9.0"
2735 },
2736 "engines": {
2737 "node": "^18.18.0 || >=20.0.0"
@@ -2743,13 +2742,13 @@
2742 }
2743 },
2744 "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/type-utils": {
2746 - "version": "7.8.0",
2747 - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz",
2748 - "integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==",
2745 + "version": "7.9.0",
2746 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.9.0.tgz",
2747 + "integrity": "sha512-6Qy8dfut0PFrFRAZsGzuLoM4hre4gjzWJB6sUvdunCYZsYemTkzZNwF1rnGea326PHPT3zn5Lmg32M/xfJfByA==",
2748 "dev": true,
2749 "dependencies": {
2751 - "@typescript-eslint/typescript-estree": "7.8.0",
2752 - "@typescript-eslint/utils": "7.8.0",
2750 + "@typescript-eslint/typescript-estree": "7.9.0",
2751 + "@typescript-eslint/utils": "7.9.0",
2752 "debug": "^4.3.4",
2753 "ts-api-utils": "^1.3.0"
2754 },
@@ -2770,9 +2769,9 @@
2769 }
2770 },
2771 "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/types": {
2773 - "version": "7.8.0",
2774 - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz",
2775 - "integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==",
2772 + "version": "7.9.0",
2773 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.9.0.tgz",
2774 + "integrity": "sha512-oZQD9HEWQanl9UfsbGVcZ2cGaR0YT5476xfWE0oE5kQa2sNK2frxOlkeacLOTh9po4AlUT5rtkGyYM5kew0z5w==",
2775 "dev": true,
2776 "engines": {
2777 "node": "^18.18.0 || >=20.0.0"
@@ -2783,13 +2782,13 @@
2782 }
2783 },
2784 "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/typescript-estree": {
2786 - "version": "7.8.0",
2787 - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz",
2788 - "integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==",
2785 + "version": "7.9.0",
2786 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.9.0.tgz",
2787 + "integrity": "sha512-zBCMCkrb2YjpKV3LA0ZJubtKCDxLttxfdGmwZvTqqWevUPN0FZvSI26FalGFFUZU/9YQK/A4xcQF9o/VVaCKAg==",
2788 "dev": true,
2789 "dependencies": {
2791 - "@typescript-eslint/types": "7.8.0",
2792 - "@typescript-eslint/visitor-keys": "7.8.0",
2790 + "@typescript-eslint/types": "7.9.0",
2791 + "@typescript-eslint/visitor-keys": "7.9.0",
2792 "debug": "^4.3.4",
2793 "globby": "^11.1.0",
2794 "is-glob": "^4.0.3",
@@ -2811,18 +2810,15 @@
2810 }
2811 },
2812 "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/utils": {
2814 - "version": "7.8.0",
2815 - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz",
2816 - "integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==",
2813 + "version": "7.9.0",
2814 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.9.0.tgz",
2815 + "integrity": "sha512-5KVRQCzZajmT4Ep+NEgjXCvjuypVvYHUW7RHlXzNPuak2oWpVoD1jf5xCP0dPAuNIchjC7uQyvbdaSTFaLqSdA==",
2816 "dev": true,
2817 "dependencies": {
2818 "@eslint-community/eslint-utils": "^4.4.0",
2820 - "@types/json-schema": "^7.0.15",
2821 - "@types/semver": "^7.5.8",
2822 - "@typescript-eslint/scope-manager": "7.8.0",
2823 - "@typescript-eslint/types": "7.8.0",
2824 - "@typescript-eslint/typescript-estree": "7.8.0",
2825 - "semver": "^7.6.0"
2819 + "@typescript-eslint/scope-manager": "7.9.0",
2820 + "@typescript-eslint/types": "7.9.0",
2821 + "@typescript-eslint/typescript-estree": "7.9.0"
2822 },
2823 "engines": {
2824 "node": "^18.18.0 || >=20.0.0"
@@ -2836,12 +2832,12 @@
2832 }
2833 },
2834 "node_modules/@vue/eslint-config-typescript/node_modules/@typescript-eslint/visitor-keys": {
2839 - "version": "7.8.0",
2840 - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz",
2841 - "integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==",
2835 + "version": "7.9.0",
2836 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.9.0.tgz",
2837 + "integrity": "sha512-iESPx2TNLDNGQLyjKhUvIKprlP49XNEK+MvIf9nIO7ZZaZdbnfWKHnXAgufpxqfA0YryH8XToi4+CjBgVnFTSQ==",
2838 "dev": true,
2839 "dependencies": {
2844 - "@typescript-eslint/types": "7.8.0",
2840 + "@typescript-eslint/types": "7.9.0",
2841 "eslint-visitor-keys": "^3.4.3"
2842 },
2843 "engines": {
@@ -3739,9 +3735,9 @@
3735 }
3736 },
3737 "node_modules/caniuse-lite": {
3742 - "version": "1.0.30001616",
3743 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001616.tgz",
3744 - "integrity": "sha512-RHVYKov7IcdNjVHJFNY/78RdG4oGVjbayxv8u5IO74Wv7Hlq4PnJE6mo/OjFijjVFNy5ijnCt6H3IIo4t+wfEw==",
3738 + "version": "1.0.30001620",
3739 + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz",
3740 + "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==",
3741 "dev": true,
3742 "funding": [
3743 {
@@ -3907,9 +3903,9 @@
3903 }
3904 },
3905 "node_modules/cli-table3": {
3910 - "version": "0.6.4",
3911 - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.4.tgz",
3912 - "integrity": "sha512-Lm3L0p+/npIQWNIiyF/nAn7T5dnOwR3xNTHXYEBFBFVPXzCVNZ5lqEC/1eo/EVfpDsQ1I+TX4ORPQgp+UI0CRw==",
3906 + "version": "0.6.5",
3907 + "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz",
3908 + "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==",
3909 "dev": true,
3910 "dependencies": {
3911 "string-width": "^4.2.0"
@@ -4894,9 +4890,9 @@
4890 "dev": true
4891 },
4892 "node_modules/electron-to-chromium": {
4897 - "version": "1.4.756",
4898 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.756.tgz",
4899 - "integrity": "sha512-RJKZ9+vEBMeiPAvKNWyZjuYyUqMndcP1f335oHqn3BEQbs2NFtVrnK5+6Xg5wSM9TknNNpWghGDUCKGYF+xWXw==",
4893 + "version": "1.4.774",
4894 + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.774.tgz",
4895 + "integrity": "sha512-132O1XCd7zcTkzS3FgkAzKmnBuNJjK8WjcTtNuoylj7MYbqw5eXehjQ5OK91g0zm7OTKIPeaAG4CPoRfD9M1Mg==",
4896 "dev": true
4897 },
4898 "node_modules/emoji-regex": {
@@ -5989,22 +5985,22 @@
5985 }
5986 },
5987 "node_modules/glob": {
5992 - "version": "10.3.12",
5993 - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
5994 - "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
5988 + "version": "10.3.15",
5989 + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz",
5990 + "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==",
5991 "dev": true,
5992 "dependencies": {
5993 "foreground-child": "^3.1.0",
5994 "jackspeak": "^2.3.6",
5995 "minimatch": "^9.0.1",
5996 "minipass": "^7.0.4",
6001 - "path-scurry": "^1.10.2"
5997 + "path-scurry": "^1.11.0"
5998 },
5999 "bin": {
6000 "glob": "dist/esm/bin.mjs"
6001 },
6002 "engines": {
6007 - "node": ">=16 || 14 >=14.17"
6003 + "node": ">=16 || 14 >=14.18"
6004 },
6005 "funding": {
6006 "url": "https://github.com/sponsors/isaacs"
@@ -6422,9 +6418,9 @@
6418 }
6419 },
6420 "node_modules/immutable": {
6425 - "version": "4.3.5",
6426 - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
6427 - "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
6421 + "version": "4.3.6",
6422 + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz",
6423 + "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==",
6424 "dev": true
6425 },
6426 "node_modules/import-fresh": {
@@ -7782,11 +7778,11 @@
7778 }
7779 },
7780 "node_modules/markdown-it-highlightjs": {
7785 - "version": "4.0.1",
7786 - "resolved": "https://registry.npmjs.org/markdown-it-highlightjs/-/markdown-it-highlightjs-4.0.1.tgz",
7787 - "integrity": "sha512-EPXwFEN6P5nqR3G4KjT20r20xbGYKMMA/360hhSYFmeoGXTE6hsLtJAiB/8ID8slVH4CWHHEL7GX0YenyIstVQ==",
7781 + "version": "4.1.0",
7782 + "resolved": "https://registry.npmjs.org/markdown-it-highlightjs/-/markdown-it-highlightjs-4.1.0.tgz",
7783 + "integrity": "sha512-aYcgme5aYn10BHEvLZaCNgwxU2oaAX9inK9dwCv38wJdq7tal5FzZrLdQQY8MR3I1H07S3BKgYGRX2kKuPT+sA==",
7784 "dependencies": {
7789 - "highlight.js": "^11.5.1"
7785 + "highlight.js": "^11.9.0"
7786 }
7787 },
7788 "node_modules/mdn-data": {
@@ -7907,9 +7903,9 @@
7903 }
7904 },
7905 "node_modules/minipass": {
7910 - "version": "7.1.0",
7911 - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.0.tgz",
7912 - "integrity": "sha512-oGZRv2OT1lO2UF1zUcwdTb3wqUwI0kBGTgt/T7OdSj6M6N5m3o5uPf0AIW6lVxGGoiWUR7e2AwTE+xiwK8WQig==",
7906 + "version": "7.1.1",
7907 + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz",
7908 + "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==",
7909 "dev": true,
7910 "engines": {
7911 "node": ">=16 || 14 >=14.17"
@@ -8230,9 +8226,9 @@
8226 }
8227 },
8228 "node_modules/nwsapi": {
8233 - "version": "2.2.9",
8234 - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.9.tgz",
8235 - "integrity": "sha512-2f3F0SEEer8bBu0dsNCFF50N0cTThV1nWFYcEYFZttdW0lDAoybv9cQoK7X7/68Z89S7FoRrVjP1LPX4XRf9vg==",
8229 + "version": "2.2.10",
8230 + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.10.tgz",
8231 + "integrity": "sha512-QK0sRs7MKv0tKe1+5uZIQk/C8XGza4DAnztJG8iD+TpJIORARrCxczA738awHrZoHeTjSSoHqao2teO0dC/gFQ==",
8232 "dev": true
8233 },
8234 "node_modules/object-assign": {
@@ -8501,16 +8497,16 @@
8497 "dev": true
8498 },
8499 "node_modules/path-scurry": {
8504 - "version": "1.10.2",
8505 - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
8506 - "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
8500 + "version": "1.11.1",
8501 + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
8502 + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
8503 "dev": true,
8504 "dependencies": {
8505 "lru-cache": "^10.2.0",
8506 "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
8507 },
8508 "engines": {
8513 - "node": ">=16 || 14 >=14.17"
8509 + "node": ">=16 || 14 >=14.18"
8510 },
8511 "funding": {
8512 "url": "https://github.com/sponsors/isaacs"
@@ -8680,13 +8676,13 @@
8676 }
8677 },
8678 "node_modules/pkg-types": {
8683 - "version": "1.1.0",
8684 - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.0.tgz",
8685 - "integrity": "sha512-/RpmvKdxKf8uILTtoOhAgf30wYbP2Qw+L9p3Rvshx1JZVX+XQNZQFjlbmGHEGIm4CkVPlSn+NXmIM8+9oWQaSA==",
8679 + "version": "1.1.1",
8680 + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.1.tgz",
8681 + "integrity": "sha512-ko14TjmDuQJ14zsotODv7dBlwxKhUKQEhuhmbqo1uCi9BB0Z2alo/wAXg6q1dTR5TyuqYyWhjtfe/Tsh+X28jQ==",
8682 "dev": true,
8683 "dependencies": {
8684 "confbox": "^0.1.7",
8689 - "mlly": "^1.6.1",
8685 + "mlly": "^1.7.0",
8686 "pathe": "^1.1.2"
8687 }
8688 },
@@ -9649,9 +9645,9 @@
9645 "dev": true
9646 },
9647 "node_modules/sass": {
9652 - "version": "1.77.1",
9653 - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.1.tgz",
9654 - "integrity": "sha512-OMEyfirt9XEfyvocduUIOlUSkWOXS/LAt6oblR/ISXCTukyavjex+zQNm51pPCOiFKY1QpWvEH1EeCkgyV3I6w==",
9648 + "version": "1.77.2",
9649 + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.2.tgz",
9650 + "integrity": "sha512-eb4GZt1C3avsX3heBNlrc7I09nyT00IUuo4eFhAbeXWU2fvA7oXI53SxODVAA+zgZCk9aunAZgO+losjR3fAwA==",
9651 "dev": true,
9652 "dependencies": {
9653 "chokidar": ">=3.0.0 <4.0.0",
@@ -9692,12 +9688,9 @@
9688 "integrity": "sha512-MW8Qs6vbzo0pHmDpFSYPna+lwpZ6Zk1ancbajw/7E8TKtHdV+1DfZZD+kKJEhG/cAoB/i+LiT+5msZOqj0DwRA=="
9689 },
9690 "node_modules/semver": {
9695 - "version": "7.6.0",
9696 - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
9697 - "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
9698 - "dependencies": {
9699 - "lru-cache": "^6.0.0"
9700 - },
9691 + "version": "7.6.2",
9692 + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
9693 + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
9694 "bin": {
9695 "semver": "bin/semver.js"
9696 },
@@ -9705,22 +9698,6 @@
9698 "node": ">=10"
9699 }
9700 },
9708 - "node_modules/semver/node_modules/lru-cache": {
9709 - "version": "6.0.0",
9710 - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
9711 - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
9712 - "dependencies": {
9713 - "yallist": "^4.0.0"
9714 - },
9715 - "engines": {
9716 - "node": ">=10"
9717 - }
9718 - },
9719 - "node_modules/semver/node_modules/yallist": {
9720 - "version": "4.0.0",
9721 - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
9722 - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
9723 - },
9701 "node_modules/set-function-length": {
9702 "version": "1.2.2",
9703 "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
@@ -10397,9 +10374,9 @@
10374 }
10375 },
10376 "node_modules/svgo": {
10400 - "version": "3.2.0",
10401 - "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.2.0.tgz",
10402 - "integrity": "sha512-4PP6CMW/V7l/GmKRKzsLR8xxjdHTV4IMvhTnpuHwwBazSIlw5W/5SmPjN8Dwyt7lKbSJrRDgp4t9ph0HgChFBQ==",
10377 + "version": "3.3.2",
10378 + "resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
10379 + "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
10380 "dev": true,
10381 "dependencies": {
10382 "@trysound/sax": "0.2.0",
@@ -11021,9 +10998,9 @@
10998 }
10999 },
11000 "node_modules/update-browserslist-db": {
11024 - "version": "1.0.15",
11025 - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.15.tgz",
11026 - "integrity": "sha512-K9HWH62x3/EalU1U6sjSZiylm9C8tgq2mSvshZpqc7QE69RaA2qjhkW2HlNA0tFpEbtyFz7HTqbSdN4MSwUodA==",
11001 + "version": "1.0.16",
11002 + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
11003 + "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==",
11004 "dev": true,
11005 "funding": [
11006 {
@@ -11041,7 +11018,7 @@
11018 ],
11019 "dependencies": {
11020 "escalade": "^3.1.2",
11044 - "picocolors": "^1.0.0"
11021 + "picocolors": "^1.0.1"
11022 },
11023 "bin": {
11024 "update-browserslist-db": "cli.js"
@@ -11511,9 +11488,9 @@
11488 }
11489 },
11490 "node_modules/vue-component-type-helpers": {
11514 - "version": "2.0.16",
11515 - "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.16.tgz",
11516 - "integrity": "sha512-qisL/iAfdO++7w+SsfYQJVPj6QKvxp4i1MMxvsNO41z/8zu3KuAw9LkhKUfP/kcOWGDxESp+pQObWppXusejCA==",
11491 + "version": "2.0.19",
11492 + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-2.0.19.tgz",
11493 + "integrity": "sha512-cN3f1aTxxKo4lzNeQAkVopswuImUrb5Iurll9Gaw5cqpnbTAxtEMM1mgi6ou4X79OCyqYv1U1mzBHJkzmiK82w==",
11494 "dev": true
11495 },
11496 "node_modules/vue-eslint-parser": {
frontend/package.json
+5 -7
@@ -54,13 +54,14 @@
54 "jose": "^5.3.0",
55 "js-md5": "^0.8.3",
56 "lodash": "^4.17.21",
57 - "markdown-it-highlightjs": "^4.0.1",
57 + "markdown-it-highlightjs": "^4.1.0",
58 "mitt": "^3.0.1",
59 "naive-ui": "^2.38.2",
60 "password-validator": "^5.3.0",
61 "pinia": "^2.1.7",
62 "pinia-plugin-persistedstate": "^3.2.1",
63 "secure-ls": "^1.2.6",
64 + "shiki": "^1.5.2",
65 "validator": "^13.12.0",
66 "vue": "^3.4.27",
67 "vue-advanced-cropper": "^2.8.8",
@@ -75,16 +76,14 @@
76 "devDependencies": {
77 "@clack/prompts": "^0.7.0",
78 "@iconify/vue": "^4.1.2",
78 - "@rushstack/eslint-patch": "^1.10.2",
79 + "@rushstack/eslint-patch": "^1.10.3",
80 "@tsconfig/node18": "^18.2.4",
81 "@types/bytes": "^3.1.4",
82 "@types/file-saver": "^2.0.7",
83 "@types/fs-extra": "^11.0.4",
83 - "@types/highlight.js": "^10.1.0",
84 - "@types/html2canvas": "^1.0.0",
84 "@types/inquirer": "^9.0.7",
85 "@types/jsdom": "^21.1.6",
87 - "@types/lodash": "^4.17.3",
86 + "@types/lodash": "^4.17.4",
87 "@types/markdown-it": "^14.1.1",
88 "@types/markdown-it-highlightjs": "^3.3.4",
89 "@types/node": "^20.12.12",
@@ -109,8 +108,7 @@
108 "picocolors": "^1.0.1",
109 "postcss": "^8.4.38",
110 "prettier": "^3.2.5",
112 - "sass": "^1.77.1",
113 - "shiki": "^1.5.2",
111 + "sass": "^1.77.2",
112 "start-server-and-test": "^2.0.3",
113 "tailwind-config-viewer": "^2.0.2",
114 "tailwindcss": "^3.4.3",
frontend/src/api/agents.ts
+34 -16
@@ -9,42 +9,60 @@ import type {
9 ScaPolicyResult
10 } from "@/types/agents.d"
11
12 +export interface AgentPayload {
13 + velociraptor_id: string
14 +}
15 +
16 export default {
13 - getAgents(id?: string) {
14 - return HttpClient.get<FlaskBaseResponse & { agents: Agent[] }>(`/agents${id ? "/" + id : ""}`)
17 + getAgents(agentId?: string) {
18 + return HttpClient.get<FlaskBaseResponse & { agents: Agent[] }>(`/agents${agentId ? "/" + agentId : ""}`)
19 },
16 - markCritical(id: string) {
17 - return HttpClient.post<FlaskBaseResponse>(`/agents/${id}/critical`)
20 + markCritical(agentId: string) {
21 + return HttpClient.post<FlaskBaseResponse>(`/agents/${agentId}/critical`)
22 },
19 - markNonCritical(id: string) {
20 - return HttpClient.post<FlaskBaseResponse>(`/agents/${id}/noncritical`)
23 + markNonCritical(agentId: string) {
24 + return HttpClient.post<FlaskBaseResponse>(`/agents/${agentId}/noncritical`)
25 },
22 - deleteAgent(id: string) {
23 - return HttpClient.delete<FlaskBaseResponse>(`/agents/${id}/delete`)
26 + deleteAgent(agentId: string) {
27 + return HttpClient.delete<FlaskBaseResponse>(`/agents/${agentId}/delete`)
28 },
29 syncAgents() {
30 return HttpClient.post<FlaskBaseResponse>(`/agents/sync`)
31 },
28 - agentVulnerabilities(id: string) {
32 + agentVulnerabilities(agentId: string) {
33 return HttpClient.get<FlaskBaseResponse & { vulnerabilities: AgentVulnerabilities[] }>(
30 - `/agents/${id}/vulnerabilities`
34 + `/agents/${agentId}/vulnerabilities`
35 )
36 },
33 - getSocCases(id: string | number, signal?: AbortSignal) {
37 + getSocCases(agentId: string | number, signal?: AbortSignal) {
38 return HttpClient.get<FlaskBaseResponse & { case_ids: number[] }>(
35 - `/agents/${id}/soc_cases`,
39 + `/agents/${agentId}/soc_cases`,
40 signal ? { signal } : {}
41 )
42 },
39 - getSCA(id: string | number, signal?: AbortSignal) {
40 - return HttpClient.get<FlaskBaseResponse & { sca: AgentSca[] }>(`/agents/${id}/sca`, signal ? { signal } : {})
43 + getSCA(agentId: string | number, signal?: AbortSignal) {
44 + return HttpClient.get<FlaskBaseResponse & { sca: AgentSca[] }>(
45 + `/agents/${agentId}/sca`,
46 + signal ? { signal } : {}
47 + )
48 },
42 - getSCAResults(id: string | number, policyId: string, signal?: AbortSignal) {
49 + getSCAResults(agentId: string | number, policyId: string, signal?: AbortSignal) {
50 return HttpClient.get<FlaskBaseResponse & { sca_policy_results: ScaPolicyResult[] }>(
44 - `/agents/${id}/sca/${policyId}`,
51 + `/agents/${agentId}/sca/${policyId}`,
52 signal ? { signal } : {}
53 )
54 },
55 + updateAgent(agentId: string, payload: AgentPayload) {
56 + return HttpClient.put<FlaskBaseResponse>(
57 + `/agents/${agentId}/update`,
58 + {},
59 + {
60 + params: {
61 + velociraptor_id: payload.velociraptor_id
62 + }
63 + }
64 + )
65 + },
66
67 // IGNORE AT THE MOMENT !
68 agentsWazuhOutdated() {
frontend/src/components/agents/AgentVelociraptorIdForm.vue new
+92
@@ -0,0 +1,92 @@
1 +<template>
2 + <div class="flex items-center gap-2">
3 + <code v-if="!editing" class="cursor-pointer text-primary-color" @click="edit()">
4 + {{ velociraptorId }}
5 + <Icon :name="loading ? LoadingIcon : EditIcon" :size="13" class="relative top-0.5" />
6 + </code>
7 + <n-input-group v-else>
8 + <n-input
9 + v-model:value="velociraptorIdModel"
10 + size="small"
11 + :disabled="loading"
12 + placeholder="Input velociraptor_id"
13 + >
14 + <template #suffix>
15 + <Icon
16 + :name="CloseIcon"
17 + :size="13"
18 + class="cursor-pointer"
19 + @click="editing = false"
20 + v-if="!loading"
21 + />
22 + </template>
23 + </n-input>
24 + <n-button type="primary" ghost :loading="loading" size="small" @click="updateAgent()">
25 + <span v-if="!loading">Save</span>
26 + </n-button>
27 + </n-input-group>
28 + </div>
29 +</template>
30 +
31 +<script setup lang="ts">
32 +import { onBeforeMount, ref, toRefs } from "vue"
33 +import Api from "@/api"
34 +import { useMessage, NInput, NButton, NInputGroup } from "naive-ui"
35 +import Icon from "@/components/common/Icon.vue"
36 +import type { Agent } from "@/types/agents"
37 +
38 +const velociraptorId = defineModel<string>("velociraptorId", { default: "" })
39 +
40 +const props = defineProps<{
41 + agent: Agent
42 +}>()
43 +const { agent } = toRefs(props)
44 +
45 +const emit = defineEmits<{
46 + (e: "updated", value: string): void
47 +}>()
48 +
49 +const LoadingIcon = "eos-icons:loading"
50 +const EditIcon = "uil:edit-alt"
51 +const CloseIcon = "carbon:close-filled"
52 +
53 +const loading = ref(false)
54 +const editing = ref(false)
55 +const message = useMessage()
56 +const velociraptorIdModel = ref<string | null>("")
57 +
58 +function edit() {
59 + editing.value = true
60 + velociraptorIdModel.value = velociraptorId.value
61 +}
62 +
63 +function updateAgent() {
64 + if (agent.value.agent_id) {
65 + loading.value = true
66 +
67 + const velociraptorIdPayload = velociraptorIdModel.value || ""
68 +
69 + Api.agents
70 + .updateAgent(agent.value.agent_id.toString(), { velociraptor_id: velociraptorIdPayload })
71 + .then(res => {
72 + if (res.data.success) {
73 + velociraptorId.value = velociraptorIdPayload
74 + editing.value = false
75 + emit("updated", velociraptorIdPayload)
76 + } else {
77 + message.warning(res.data?.message || "An error occurred. Please try again later.")
78 + }
79 + })
80 + .catch(err => {
81 + message.error(err.response?.data?.message || "An error occurred. Please try again later.")
82 + })
83 + .finally(() => {
84 + loading.value = false
85 + })
86 + }
87 +}
88 +
89 +onBeforeMount(() => {
90 + velociraptorIdModel.value = velociraptorId.value || ""
91 +})
92 +</script>
frontend/src/components/agents/OverviewSection.vue
+16 -11
@@ -4,16 +4,18 @@
4 <KVCard v-for="item of propsSanitized" :key="item.key">
5 <template #key>{{ item.key }}</template>
6 <template #value>
7 - <template v-if="item.key === 'customer_code'">
8 - <code
9 - class="cursor-pointer text-primary-color"
10 - @click="gotoCustomer({ code: item.val })"
11 - v-if="item.val && item.val !== '-'"
12 - >
7 + <template v-if="item.key === 'customer_code' && item.val !== '-'">
8 + <code class="cursor-pointer text-primary-color" @click="gotoCustomer({ code: item.val })">
9 {{ item.val }}
10 <Icon :name="LinkIcon" :size="13" class="relative top-0.5" />
11 </code>
16 - <span v-else>-</span>
12 + </template>
13 + <template v-else-if="item.key === 'velociraptor_id'">
14 + <AgentVelociraptorIdForm
15 + v-model:velociraptorId="item.val"
16 + :agent="agent"
17 + @updated="emit('updated')"
18 + />
19 </template>
20 <template v-else>
21 {{ item.val ?? "-" }}
@@ -32,6 +34,11 @@ import { useSettingsStore } from "@/stores/settings"
34 import KVCard from "@/components/common/KVCard.vue"
35 import Icon from "@/components/common/Icon.vue"
36 import { useGoto } from "@/composables/useGoto"
37 +import AgentVelociraptorIdForm from "./AgentVelociraptorIdForm.vue"
38 +
39 +const emit = defineEmits<{
40 + (e: "updated"): void
41 +}>()
42
43 const props = defineProps<{
44 agent: Agent
@@ -46,11 +53,9 @@ const propsSanitized = computed(() => {
53 const obj = []
54 for (const key in agent.value) {
55 if (["wazuh_last_seen", "velociraptor_last_seen"].includes(key)) {
49 - // @ts-ignore
50 - obj.push({ key, val: formatDate(agent.value[key], dFormats.datetime) || "-" })
56 + obj.push({ key, val: formatDate(Reflect.get(agent.value, key), dFormats.datetime) || "-" })
57 } else {
52 - // @ts-ignore
53 - obj.push({ key, val: agent.value[key] || "-" })
58 + obj.push({ key, val: Reflect.get(agent.value, key) || "-" })
59 }
60 }
61
frontend/src/components/common/Markdown.vue
+1
@@ -9,6 +9,7 @@
9 </template>
10
11 <script setup lang="ts">
12 +// TODO: replace highlightjs with shiki
13 import { toRefs } from "vue"
14 import markdownItHighlightjs from "markdown-it-highlightjs/core"
15 import "@/assets/scss/hljs.scss"
frontend/src/views/agents/Overview.vue
+4 -5
@@ -46,10 +46,10 @@
46 </n-spin>
47 <n-card class="py-1 px-4 pb-4" content-style="padding:0">
48 <n-spin :show="loadingAgent">
49 - <n-tabs type="line" animated default-value="SCA">
49 + <n-tabs type="line" animated default-value="Overview">
50 <n-tab-pane name="Overview" tab="Overview" display-directive="show">
51 <div class="section">
52 - <OverviewSection v-if="agent" :agent="agent" />
52 + <OverviewSection v-if="agent" :agent="agent" @updated="getAgent()" />
53 </div>
54 </n-tab-pane>
55 <n-tab-pane name="Vulnerabilities" tab="Vulnerabilities" display-directive="show:lazy">
@@ -113,18 +113,17 @@
113
114 <script setup lang="ts">
115 import { ref, onBeforeMount, computed, nextTick } from "vue"
116 -import { useRoute } from "vue-router"
116 +import { useMessage, NSpin, NTooltip, NButton, NTabs, NTabPane, NCard, useDialog } from "naive-ui"
117 +import { useRoute, useRouter } from "vue-router"
118 import Api from "@/api"
119 import { AgentStatus, type Agent } from "@/types/agents.d"
120 import { handleDeleteAgent, toggleAgentCritical } from "@/components/agents/utils"
120 -import { useRouter } from "vue-router"
121 import VulnerabilitiesGrid from "@/components/agents/vulnerabilities/VulnerabilitiesGrid.vue"
122 import ScaTable from "@/components/agents/sca/ScaTable.vue"
123 import AlertsList from "@/components/alerts/AlertsList.vue"
124 import OverviewSection from "@/components/agents/OverviewSection.vue"
125 import AgentCases from "@/components/agents/AgentCases.vue"
126 import AgentFlowList from "@/components/agents/agentFlow/AgentFlowList.vue"
127 -import { useMessage, NSpin, NTooltip, NButton, NTabs, NTabPane, NCard, useDialog } from "naive-ui"
127 import Icon from "@/components/common/Icon.vue"
128 import type { Artifact } from "@/types/artifacts.d"
129 import ArtifactsCollect from "@/components/artifacts/ArtifactsCollect.vue"
frontend/src/vite-env.d.ts
-2
@@ -12,8 +12,6 @@ declare module "*.svg" {
12 export default component
13 }
14
15 -declare module "v-calendar"
16 -
15 declare module "markdown-it-highlightjs/core" {
16 export { default } from "markdown-it-highlightjs/types/core.d.ts"
17 }