Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Wazuh agent upgrade #225

Merged
merged 9 commits into from
May 29, 2024
4 changes: 2 additions & 2 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
}
42 changes: 40 additions & 2 deletions backend/app/agents/routes/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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",
Expand Down
4 changes: 4 additions & 0 deletions backend/app/agents/schema/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,3 +52,7 @@ class AgentUpdateCustomerCodeBody(BaseModel):
class AgentUpdateCustomerCodeResponse(BaseModel):
success: bool
message: str

class AgentWazuhUpgradeResponse(BaseModel):
success: bool
message: str
66 changes: 64 additions & 2 deletions backend/app/agents/wazuh/services/agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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}",
)
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"))],
)
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
3 changes: 3 additions & 0 deletions frontend/src/api/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,9 @@ export default {
}
)
},
upgradeWazuhAgent(agentId: string) {
return HttpClient.post<FlaskBaseResponse>(`/agents/${agentId}/wazuh/upgrade`)
},

// IGNORE AT THE MOMENT !
agentsWazuhOutdated() {
Expand Down
7 changes: 5 additions & 2 deletions frontend/src/api/monitoringAlerts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ export default {
invoke(alertId: number) {
return HttpClient.post<FlaskBaseResponse>(`/monitoring_alert/invoke/${alertId}`)
},
delete(alertId: number) {
return HttpClient.delete<FlaskBaseResponse>(`/monitoring_alert/${alertId}`)
deleteAlert(alertId: number) {
return HttpClient.delete<FlaskBaseResponse>(`/monitoring_alert/single_alert/${alertId}`)
},
purge() {
return HttpClient.delete<FlaskBaseResponse>(`/monitoring_alert/purge`)
}
}
6 changes: 1 addition & 5 deletions frontend/src/components/agents/OverviewSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,7 @@
</code>
</template>
<template v-else-if="item.key === 'velociraptor_id'">
<AgentVelociraptorIdForm
v-model:velociraptorId="item.val"
:agent="agent"
@updated="emit('updated')"
/>
<AgentVelociraptorIdForm v-model:velociraptorId="item.val" :agent @updated="emit('updated')" />
</template>
<template v-else>
{{ item.val ?? "-" }}
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/agents/sca/ScaItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@
</n-tab-pane>
<n-tab-pane name="SCA Results" tab="SCA Results" display-directive="show:lazy">
<div class="p-7 pt-4">
<ScaResults :sca="sca" :agent="agent" />
<ScaResults :sca="sca" :agent />
</div>
</n-tab-pane>
</n-tabs>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/agents/sca/ScaTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@
segmented
content-class="!p-0"
>
<ScaItem v-if="selectedSca" :sca="selectedSca" :agent="agent"></ScaItem>
<ScaItem v-if="selectedSca" :sca="selectedSca" :agent></ScaItem>
</n-modal>
</n-spin>
</template>
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/components/monitoringAlerts/ItemActions.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
55 changes: 54 additions & 1 deletion frontend/src/components/monitoringAlerts/List.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,20 @@
</div>
</div>
</n-popover>

<n-button
size="small"
type="error"
ghost
@click="handlePurge()"
:loading="loadingPurge"
v-if="monitoringAlerts.length"
>
<div class="flex items-center gap-2">
<Icon :name="TrashIcon" :size="16"></Icon>
<span class="hidden xs:block">Purge</span>
</div>
</n-button>
</div>
<n-pagination
v-model:page="currentPage"
Expand Down Expand Up @@ -61,14 +75,16 @@

<script setup lang="ts">
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<MonitoringAlert[]>([])

Expand All @@ -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<number>(() => {
Expand Down Expand Up @@ -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
Expand Down
9 changes: 8 additions & 1 deletion frontend/src/components/soc/SocCases/SocCasesList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,14 @@
</div>
</n-popover>

<n-button size="small" type="error" ghost @click="handlePurge()" :loading="loadingPurge">
<n-button
size="small"
type="error"
ghost
@click="handlePurge()"
:loading="loadingPurge"
v-if="casesList.length"
>
<div class="flex items-center gap-2">
<Icon :name="TrashIcon" :size="16"></Icon>
<span class="hidden xs:block">Purge</span>
Expand Down
Loading
Loading