From 58c41eff5aae14eab7ab053d15a7fb41b3ae9523 Mon Sep 17 00:00:00 2001 From: Taylor Date: Tue, 28 May 2024 09:08:28 -0500 Subject: [PATCH 1/8] feat: Add endpoint for upgrading Wazuh agent This commit adds a new endpoint `/upgrade` to upgrade a Wazuh agent in the `agents.py` file. The endpoint requires the `agent_id` as a parameter and upgrades the corresponding agent. The upgrade process may take a few minutes to complete. This feature improves the functionality of the application by allowing users to easily upgrade Wazuh agents. --- backend/app/agents/routes/agents.py | 42 ++++++++++++- backend/app/agents/schema/agents.py | 4 ++ backend/app/agents/wazuh/services/agents.py | 66 ++++++++++++++++++++- 3 files changed, 108 insertions(+), 4 deletions(-) diff --git a/backend/app/agents/routes/agents.py b/backend/app/agents/routes/agents.py index 2485ff68..3449ac33 100644 --- a/backend/app/agents/routes/agents.py +++ b/backend/app/agents/routes/agents.py @@ -11,7 +11,7 @@ from sqlalchemy.future import select from app.agents.dfir_iris.services.cases import collect_agent_soc_cases -from app.agents.schema.agents import AgentModifyResponse +from app.agents.schema.agents import AgentModifyResponse, AgentWazuhUpgradeResponse from app.agents.schema.agents import AgentsResponse from app.agents.schema.agents import OutdatedVelociraptorAgentsResponse from app.agents.schema.agents import OutdatedWazuhAgentsResponse @@ -24,7 +24,7 @@ from app.agents.wazuh.schema.agents import WazuhAgentScaPolicyResultsResponse from app.agents.wazuh.schema.agents import WazuhAgentScaResponse from app.agents.wazuh.schema.agents import WazuhAgentVulnerabilitiesResponse -from app.agents.wazuh.services.agents import delete_agent_wazuh +from app.agents.wazuh.services.agents import delete_agent_wazuh, upgrade_wazuh_agent from app.agents.wazuh.services.sca import collect_agent_sca from app.agents.wazuh.services.sca import collect_agent_sca_policy_results from app.agents.wazuh.services.vulnerabilities import collect_agent_vulnerabilities @@ -348,6 +348,44 @@ async def mark_agent_as_not_critical( detail=f"Failed to mark agent as not critical: {str(e)}", ) +@agents_router.post( + "/{agent_id}/wazuh/upgrade", + response_model=AgentModifyResponse, + description="Upgrade wazuh agent", + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], +) +async def upgrade_wazuh_agent_route( + agent_id: str, + session: AsyncSession = Depends(get_db), +) -> AgentWazuhUpgradeResponse: + """ + Upgrade Wazuh agent. + + Args: + agent_id (str): The ID of the agent to be upgraded. + session (AsyncSession, optional): The database session. Defaults to Depends(get_db). + + Returns: + AgentModifyResponse: The response indicating the success or failure of the operation. + """ + logger.info(f"Upgrading Wazuh agent {agent_id}") + try: + result = await session.execute(select(Agents).filter(Agents.agent_id == agent_id)) + agent = result.scalars().first() + if not agent: + raise HTTPException(status_code=404, detail=f"Agent with agent_id {agent_id} not found") + return await upgrade_wazuh_agent(agent_id) + return AgentWazuhUpgradeResponse( + success=True, + message=f"Agent {agent_id} upgraded successfully started. Upgrade may take a few minutes to complete.", + ) + except Exception as e: + logger.error(f"Failed to upgrade Wazuh agent {agent_id}: {e}") + raise HTTPException( + status_code=500, + detail=f"Failed to upgrade Wazuh agent {agent_id}: {e}", + ) + @agents_router.get( "/{agent_id}/vulnerabilities", diff --git a/backend/app/agents/schema/agents.py b/backend/app/agents/schema/agents.py index 88570cf1..ad32fe59 100644 --- a/backend/app/agents/schema/agents.py +++ b/backend/app/agents/schema/agents.py @@ -52,3 +52,7 @@ class AgentUpdateCustomerCodeBody(BaseModel): class AgentUpdateCustomerCodeResponse(BaseModel): success: bool message: str + +class AgentWazuhUpgradeResponse(BaseModel): + success: bool + message: str diff --git a/backend/app/agents/wazuh/services/agents.py b/backend/app/agents/wazuh/services/agents.py index ac8d42d3..824d1bbb 100644 --- a/backend/app/agents/wazuh/services/agents.py +++ b/backend/app/agents/wazuh/services/agents.py @@ -3,11 +3,11 @@ from fastapi import HTTPException from loguru import logger -from app.agents.schema.agents import AgentModifyResponse +from app.agents.schema.agents import AgentModifyResponse, AgentWazuhUpgradeResponse from app.agents.wazuh.schema.agents import WazuhAgent from app.agents.wazuh.schema.agents import WazuhAgentsList from app.connectors.wazuh_manager.utils.universal import send_delete_request -from app.connectors.wazuh_manager.utils.universal import send_get_request +from app.connectors.wazuh_manager.utils.universal import send_get_request, send_put_request async def collect_wazuh_agents() -> WazuhAgentsList: @@ -145,3 +145,65 @@ async def delete_agent_wazuh(agent_id: str) -> AgentModifyResponse: status_code=500, detail=f"Failed to delete agent {agent_id} from Wazuh Manager: {e}", ) + +def handle_agent_upgrade_response(agent_upgraded: dict) -> AgentWazuhUpgradeResponse: + """ + Handle the response from the agent upgrade request. + + Args: + agent_upgraded (dict): The response from the agent upgrade request. + + Returns: + AgentWazuhUpgradeResponse: The response indicating the status of the agent upgrade. + """ + data = agent_upgraded.get('data', {}).get('data', {}) + total_failed_items = data.get('total_failed_items', 0) + + if total_failed_items == 0: + # Upgrade was successful + return AgentWazuhUpgradeResponse( + success=True, + message=agent_upgraded.get('data', {}).get('message', 'Unknown error'), + ) + else: + # Upgrade failed + failed_items = data.get('failed_items', [{}]) + error_message = failed_items[0].get('error', {}).get('message', 'Unknown error') + return AgentWazuhUpgradeResponse( + success=False, + message=error_message, + ) + +async def upgrade_wazuh_agent(agent_id: str) -> AgentWazuhUpgradeResponse: + """Upgrade agent from Wazuh Manager. + + Args: + agent_id (str): The ID of the agent to be upgraded. + + Returns: + AgentWazuhUpgradeResponse: The response indicating the status of the agent upgrade. + + Raises: + HTTPException: If there is an HTTP error during the upgrade process. + """ + logger.info(f"Upgrading agent {agent_id} from Wazuh Manager") + + params = { + "agents_list": [agent_id], + } + + try: + agent_upgraded = await send_put_request(endpoint="agents/upgrade", data=None, params=params) + logger.info(f"Agent upgrade response: {agent_upgraded}") + return handle_agent_upgrade_response(agent_upgraded) + + except HTTPException as http_e: + # * Catch any HTTPException and re-raise it + raise http_e + + except Exception as e: + # * Catch-all for other exceptions + raise HTTPException( + status_code=500, + detail=f"Failed to upgrade agent {agent_id} from Wazuh Manager: {e}", + ) From cae2628462c29afab3fd42c25569146e8019229b Mon Sep 17 00:00:00 2001 From: Davide Di Modica Date: Wed, 29 May 2024 10:23:57 +0200 Subject: [PATCH 2/8] added Upgrade wazuh agent button --- .vscode/extensions.json | 4 +- frontend/src/api/agents.ts | 3 + .../src/components/agents/OverviewSection.vue | 6 +- .../src/components/agents/sca/ScaItem.vue | 2 +- .../src/components/agents/sca/ScaTable.vue | 2 +- frontend/src/views/agents/Overview.vue | 85 +++++++++++++------ 6 files changed, 66 insertions(+), 36 deletions(-) diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8863c1ec..f96cea68 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -9,9 +9,9 @@ "esbenp.prettier-vscode", "ms-vscode.remtoe-remote-wsl", "dbaeumer.vscode-eslint", - "Vue.volar", "Gruntfuggly.todo-tree", "usernamehw.errorlens", - "streetsidesoftware.code-spell-checker" + "streetsidesoftware.code-spell-checker", + "vue.volar" ] } diff --git a/frontend/src/api/agents.ts b/frontend/src/api/agents.ts index ab47f90f..cad87c24 100644 --- a/frontend/src/api/agents.ts +++ b/frontend/src/api/agents.ts @@ -63,6 +63,9 @@ export default { } ) }, + upgradeWazuhAgent(agentId: string) { + return HttpClient.post(`/agents/${agentId}/wazuh/upgrade`) + }, // IGNORE AT THE MOMENT ! agentsWazuhOutdated() { diff --git a/frontend/src/components/agents/OverviewSection.vue b/frontend/src/components/agents/OverviewSection.vue index 480d84f8..9706f1be 100644 --- a/frontend/src/components/agents/OverviewSection.vue +++ b/frontend/src/components/agents/OverviewSection.vue @@ -11,11 +11,7 @@ diff --git a/frontend/src/views/agents/Overview.vue b/frontend/src/views/agents/Overview.vue index 8dafbe4e..d306a864 100644 --- a/frontend/src/views/agents/Overview.vue +++ b/frontend/src/views/agents/Overview.vue @@ -9,40 +9,48 @@ -
-
- - Toggle Critical Assets - - -
+
+
+
+ + Toggle Critical Assets + + +
-

- {{ agent?.hostname }} -

+

+ {{ agent?.hostname }} +

- ONLINE + ONLINE - - - QUARANTINED - + + + QUARANTINED + +
+
Agent #{{ agent?.agent_id }}
+
+
+ + Upgrade Agent +
-
Agent #{{ agent?.agent_id }}
@@ -142,6 +150,7 @@ const router = useRouter() const dialog = useDialog() const route = useRoute() const loadingAgent = ref(false) +const upgradingAgent = ref(false) const agent = ref(null) const agentId = ref(null) @@ -179,6 +188,28 @@ function getAgent() { } } +function upgradeWazuhAgent() { + if (agentId.value) { + upgradingAgent.value = true + + Api.agents + .upgradeWazuhAgent(agentId.value) + .then(res => { + if (res.data.success) { + message.success(res.data?.message || "Agent upgraded successfully") + } else { + message.error(res.data?.message || "An error occurred. Please try again later.") + } + }) + .catch(err => { + message.error(err.response?.data?.message || "An error occurred. Please try again later.") + }) + .finally(() => { + upgradingAgent.value = false + }) + } +} + function toggleCritical(agentId: string, criticalStatus: boolean) { toggleAgentCritical({ agentId, From 51283a5ad6c800ace871b1b15752f72098f0a769 Mon Sep 17 00:00:00 2001 From: Davide Di Modica Date: Wed, 29 May 2024 15:22:39 +0200 Subject: [PATCH 3/8] updated Upgrade Wazuh Agent button --- frontend/src/views/agents/Overview.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/views/agents/Overview.vue b/frontend/src/views/agents/Overview.vue index d306a864..7efe8d88 100644 --- a/frontend/src/views/agents/Overview.vue +++ b/frontend/src/views/agents/Overview.vue @@ -47,8 +47,8 @@
Agent #{{ agent?.agent_id }}
- - Upgrade Agent + + Upgrade Wazuh Agent
From 6814993b465bc99420387dde401d6da34869e00c Mon Sep 17 00:00:00 2001 From: Taylor Date: Wed, 29 May 2024 08:32:04 -0500 Subject: [PATCH 4/8] feat: Add endpoint for purging monitoring alerts This commit adds a new endpoint `/purge` to the `monitoring_alert.py` file. The endpoint allows users with admin or analyst scopes to purge all monitoring alerts from the database. It retrieves all monitoring alerts, deletes them from the database, and returns the purged alerts in the response. This feature improves the functionality of the application by providing a convenient way to remove all monitoring alerts when needed. --- .../routes/monitoring_alert.py | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/backend/app/integrations/monitoring_alert/routes/monitoring_alert.py b/backend/app/integrations/monitoring_alert/routes/monitoring_alert.py index 4eb73d81..4dadfeff 100644 --- a/backend/app/integrations/monitoring_alert/routes/monitoring_alert.py +++ b/backend/app/integrations/monitoring_alert/routes/monitoring_alert.py @@ -7,6 +7,7 @@ from loguru import logger from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.future import select +from sqlalchemy.sql.expression import delete from app.auth.utils import AuthHandler from app.db.db_session import get_db @@ -152,7 +153,7 @@ async def invoke_monitoring_alert( @monitoring_alerts_router.delete( - "/{monitoring_alert_id}", + "/single_alert/{monitoring_alert_id}", response_model=MonitoringAlertsResponseModel, dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], ) @@ -187,6 +188,40 @@ async def delete_monitoring_alert( message="Monitoring alert deleted successfully", ) +@monitoring_alerts_router.delete( + "/purge", + response_model=MonitoringAlertsResponseModel, + dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], +) +async def purge_monitoring_alerts( + session: AsyncSession = Depends(get_db), +) -> MonitoringAlertsResponseModel: + """ + Purge all monitoring alerts. + + Args: + session (AsyncSession, optional): The database session. Defaults to Depends(get_db). + + Returns: + MonitoringAlertsResponseModel: The monitoring alerts that were purged. + """ + logger.info("Purging monitoring alerts") + + monitoring_alerts = await session.execute(select(MonitoringAlerts)) + monitoring_alerts = monitoring_alerts.scalars().all() + + if not monitoring_alerts: + raise HTTPException(status_code=404, detail="No monitoring alerts found") + + await session.execute(delete(MonitoringAlerts)) + await session.commit() + + return MonitoringAlertsResponseModel( + monitoring_alerts=monitoring_alerts, + success=True, + message="Monitoring alerts purged successfully", + ) + @monitoring_alerts_router.post("/create", response_model=GraylogPostResponse) async def create_monitoring_alert( From fad11340e339676c53c6803a628b8ef3f07bdc17 Mon Sep 17 00:00:00 2001 From: Taylor Date: Wed, 29 May 2024 08:33:53 -0500 Subject: [PATCH 5/8] refactor: Update monitoring alert delete endpoint URL This commit updates the URL for the delete endpoint in the `monitoringAlerts.ts` file. The previous URL was `/monitoring_alert/{alertId}`, and it has been changed to `/monitoring_alert/single_alert/{alertId}`. This change improves the clarity and consistency of the endpoint URL, making it more descriptive and aligned with the purpose of the endpoint. --- frontend/src/api/monitoringAlerts.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/api/monitoringAlerts.ts b/frontend/src/api/monitoringAlerts.ts index b1f97708..f0805675 100644 --- a/frontend/src/api/monitoringAlerts.ts +++ b/frontend/src/api/monitoringAlerts.ts @@ -53,6 +53,6 @@ export default { return HttpClient.post(`/monitoring_alert/invoke/${alertId}`) }, delete(alertId: number) { - return HttpClient.delete(`/monitoring_alert/${alertId}`) + return HttpClient.delete(`/monitoring_alert/single_alert/${alertId}`) } } From 2c1d1e9a39fd9648b459bc8a13da35d7c2ad1d4e Mon Sep 17 00:00:00 2001 From: Davide Di Modica Date: Wed, 29 May 2024 16:30:56 +0200 Subject: [PATCH 6/8] added monitoring_alert purge button --- .../src/components/monitoringAlerts/List.vue | 55 ++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/frontend/src/components/monitoringAlerts/List.vue b/frontend/src/components/monitoringAlerts/List.vue index 018b327a..6a45b333 100644 --- a/frontend/src/components/monitoringAlerts/List.vue +++ b/frontend/src/components/monitoringAlerts/List.vue @@ -19,6 +19,20 @@ + + +
+ + +
+
import { ref, onBeforeMount, computed } from "vue" -import { useMessage, NSpin, NPopover, NButton, NEmpty, NPagination } from "naive-ui" +import { useMessage, NSpin, NPopover, NButton, NEmpty, NPagination, useDialog } from "naive-ui" import Api from "@/api" import Icon from "@/components/common/Icon.vue" import { useResizeObserver } from "@vueuse/core" import Alert from "./Item.vue" import type { MonitoringAlert } from "@/types/monitoringAlerts.d" +const dialog = useDialog() const message = useMessage() +const loadingPurge = ref(false) const loading = ref(false) const monitoringAlerts = ref([]) @@ -87,6 +103,7 @@ const itemsPaginated = computed(() => { return monitoringAlerts.value.slice(from, to) }) +const TrashIcon = "carbon:trash-can" const InfoIcon = "carbon:information" const total = computed(() => { @@ -115,6 +132,42 @@ function getData() { }) } +function handlePurge() { + dialog.warning({ + title: "Confirm", + content: "This will remove ALL Pending Alerts, are you sure you want to proceed?", + positiveText: "Yes I'm sure", + negativeText: "Cancel", + onPositiveClick: () => { + purge() + }, + onNegativeClick: () => { + message.info("Purge canceled") + } + }) +} + +function purge() { + loadingPurge.value = true + + Api.monitoringAlerts + .purge() + .then(res => { + if (res.data.success) { + getData() + message.success(res.data?.message || "Pending Alerts purged successfully") + } else { + message.warning(res.data?.message || "An error occurred. Please try again later.") + } + }) + .catch(err => { + message.error(err.response?.data?.message || "An error occurred. Please try again later.") + }) + .finally(() => { + loadingPurge.value = false + }) +} + useResizeObserver(header, entries => { const entry = entries[0] const { width } = entry.contentRect From df6bba90e6cf2801d95ce70105625bfacae12c08 Mon Sep 17 00:00:00 2001 From: Davide Di Modica Date: Wed, 29 May 2024 16:31:04 +0200 Subject: [PATCH 7/8] refactor --- frontend/src/api/monitoringAlerts.ts | 5 ++++- frontend/src/components/monitoringAlerts/ItemActions.vue | 2 +- frontend/src/components/soc/SocCases/SocCasesList.vue | 9 ++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/frontend/src/api/monitoringAlerts.ts b/frontend/src/api/monitoringAlerts.ts index f0805675..013805bd 100644 --- a/frontend/src/api/monitoringAlerts.ts +++ b/frontend/src/api/monitoringAlerts.ts @@ -52,7 +52,10 @@ export default { invoke(alertId: number) { return HttpClient.post(`/monitoring_alert/invoke/${alertId}`) }, - delete(alertId: number) { + deleteAlert(alertId: number) { return HttpClient.delete(`/monitoring_alert/single_alert/${alertId}`) + }, + purge() { + return HttpClient.delete(`/monitoring_alert/purge`) } } diff --git a/frontend/src/components/monitoringAlerts/ItemActions.vue b/frontend/src/components/monitoringAlerts/ItemActions.vue index a6665387..0772c268 100644 --- a/frontend/src/components/monitoringAlerts/ItemActions.vue +++ b/frontend/src/components/monitoringAlerts/ItemActions.vue @@ -109,7 +109,7 @@ function deleteAlert() { loadingDelete.value = true Api.monitoringAlerts - .delete(alert.id) + .deleteAlert(alert.id) .then(res => { if (res.data.success) { emit("deleted") diff --git a/frontend/src/components/soc/SocCases/SocCasesList.vue b/frontend/src/components/soc/SocCases/SocCasesList.vue index 912f6edd..5760796f 100644 --- a/frontend/src/components/soc/SocCases/SocCasesList.vue +++ b/frontend/src/components/soc/SocCases/SocCasesList.vue @@ -20,7 +20,14 @@ - +
From 1ab4b3863909eb71be6cda9db26ed98b69c63586 Mon Sep 17 00:00:00 2001 From: Taylor Date: Wed, 29 May 2024 09:34:24 -0500 Subject: [PATCH 8/8] refactor: Update get_current_process_names function in wazuh.py This commit updates the `get_current_process_names` function in the `wazuh.py` file. The function now uses the `get` method with default values to safely retrieve the `process_name` from the alert context. This change improves the robustness of the function and ensures that it returns an empty list if the necessary data is not available. --- backend/app/integrations/monitoring_alert/services/wazuh.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/app/integrations/monitoring_alert/services/wazuh.py b/backend/app/integrations/monitoring_alert/services/wazuh.py index 9d8da795..f1d883c2 100644 --- a/backend/app/integrations/monitoring_alert/services/wazuh.py +++ b/backend/app/integrations/monitoring_alert/services/wazuh.py @@ -508,7 +508,7 @@ async def get_current_process_names(client, alert_client, iris_alert_id): alert_client.get_alert, iris_alert_id, ) - return result["data"]["alert_context"]["process_name"] + return result.get("data", {}).get("alert_context", {}).get("process_name", []) async def get_current_alert_context(client, alert_client, iris_alert_id):