| 1 | from typing import List |
| 2 | |
| 3 | from fastapi import APIRouter |
| 4 | from fastapi import Depends |
| 5 | from fastapi import HTTPException |
| 6 | from fastapi import Security |
| 7 | from loguru import logger |
| 8 | |
| 9 | from app.auth.utils import AuthHandler |
| 10 | from app.connectors.wazuh_indexer.schema.alerts import AlertsByHostResponse |
| 11 | from app.connectors.wazuh_indexer.schema.alerts import AlertsByRulePerHostResponse |
| 12 | from app.connectors.wazuh_indexer.schema.alerts import AlertsByRuleResponse |
| 13 | from app.connectors.wazuh_indexer.schema.alerts import AlertsSearchBody |
| 14 | from app.connectors.wazuh_indexer.schema.alerts import AlertsSearchResponse |
| 15 | from app.connectors.wazuh_indexer.schema.alerts import GraylogAlertsSearchBody |
| 16 | from app.connectors.wazuh_indexer.schema.alerts import HostAlertsSearchBody |
| 17 | from app.connectors.wazuh_indexer.schema.alerts import HostAlertsSearchResponse |
| 18 | from app.connectors.wazuh_indexer.schema.alerts import IndexAlertsSearchBody |
| 19 | from app.connectors.wazuh_indexer.schema.alerts import IndexAlertsSearchResponse |
| 20 | from app.connectors.wazuh_indexer.services.alerts import get_alerts |
| 21 | from app.connectors.wazuh_indexer.services.alerts import get_alerts_by_host |
| 22 | from app.connectors.wazuh_indexer.services.alerts import get_alerts_by_rule |
| 23 | from app.connectors.wazuh_indexer.services.alerts import get_alerts_by_rule_per_host |
| 24 | from app.connectors.wazuh_indexer.services.alerts import get_graylog_alerts |
| 25 | from app.connectors.wazuh_indexer.services.alerts import get_host_alerts |
| 26 | from app.connectors.wazuh_indexer.services.alerts import get_index_alerts |
| 27 | from app.connectors.wazuh_indexer.utils.universal import collect_indices |
| 28 | |
| 29 | # App specific imports |
| 30 | |
| 31 | |
| 32 | wazuh_indexer_alerts_router = APIRouter() |
| 33 | |
| 34 | |
| 35 | async def get_index_names() -> List[str]: |
| 36 | """ |
| 37 | Retrieves a list of index names. |
| 38 | |
| 39 | Returns: |
| 40 | A list of index names. |
| 41 | """ |
| 42 | indices = await collect_indices() |
| 43 | return indices.indices_list |
| 44 | |
| 45 | |
| 46 | async def verify_index_name( |
| 47 | index_alerts_search_body: IndexAlertsSearchBody, |
| 48 | ) -> IndexAlertsSearchBody: |
| 49 | """ |
| 50 | Verifies if the given index name is managed by Wazuh Indexer or still exists. |
| 51 | |
| 52 | Args: |
| 53 | index_alerts_search_body (IndexAlertsSearchBody): The search body containing the index name. |
| 54 | |
| 55 | Raises: |
| 56 | HTTPException: If the index name is not managed by Wazuh Indexer or no longer exists. |
| 57 | |
| 58 | Returns: |
| 59 | IndexAlertsSearchBody: The search body with the verified index name. |
| 60 | """ |
| 61 | # Remove any extra spaces from index_name |
| 62 | index_alerts_search_body.index_name = index_alerts_search_body.index_name.strip() |
| 63 | |
| 64 | managed_index_names = await get_index_names() |
| 65 | if index_alerts_search_body.index_name not in managed_index_names: |
| 66 | raise HTTPException( |
| 67 | status_code=400, |
| 68 | detail=f"Index name '{index_alerts_search_body.index_name}' is not managed by Wazuh Indexer or no longer exists.", |
| 69 | ) |
| 70 | return index_alerts_search_body |
| 71 | |
| 72 | |
| 73 | @wazuh_indexer_alerts_router.post( |
| 74 | "", |
| 75 | response_model=AlertsSearchResponse, |
| 76 | description="Get all alerts", |
| 77 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 78 | ) |
| 79 | async def get_all_alerts(alerts_search_body: AlertsSearchBody) -> AlertsSearchResponse: |
| 80 | """ |
| 81 | Get all alerts. |
| 82 | |
| 83 | Args: |
| 84 | alerts_search_body (AlertsSearchBody): The search body containing filters and query parameters. |
| 85 | |
| 86 | Returns: |
| 87 | AlertsSearchResponse: The response containing the search results. |
| 88 | """ |
| 89 | logger.info("Fetching all alerts") |
| 90 | return await get_alerts(alerts_search_body) |
| 91 | |
| 92 | |
| 93 | @wazuh_indexer_alerts_router.post( |
| 94 | "/host", |
| 95 | response_model=HostAlertsSearchResponse, |
| 96 | description="Get all alerts for a host", |
| 97 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 98 | ) |
| 99 | async def get_all_alerts_for_host( |
| 100 | host_alerts_search_body: HostAlertsSearchBody, |
| 101 | ) -> HostAlertsSearchResponse: |
| 102 | """ |
| 103 | Get all alerts for a specific host. |
| 104 | |
| 105 | Args: |
| 106 | host_alerts_search_body (HostAlertsSearchBody): The request body containing the agent name. |
| 107 | |
| 108 | Returns: |
| 109 | HostAlertsSearchResponse: The response containing the host alerts. |
| 110 | """ |
| 111 | logger.info(f"Fetching all alerts for host {host_alerts_search_body.agent_name}") |
| 112 | return await get_host_alerts(host_alerts_search_body) |
| 113 | |
| 114 | |
| 115 | @wazuh_indexer_alerts_router.post( |
| 116 | "/index", |
| 117 | response_model=IndexAlertsSearchResponse, |
| 118 | description="Get all alerts for an index", |
| 119 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 120 | ) |
| 121 | async def get_all_alerts_for_index( |
| 122 | index_alerts_search_body: IndexAlertsSearchBody = Depends(verify_index_name), |
| 123 | ) -> IndexAlertsSearchResponse: |
| 124 | """ |
| 125 | Fetches all alerts for a given index. |
| 126 | |
| 127 | Args: |
| 128 | index_alerts_search_body (IndexAlertsSearchBody): The request body containing the index name. |
| 129 | |
| 130 | Returns: |
| 131 | IndexAlertsSearchResponse: The response containing the alerts for the index. |
| 132 | """ |
| 133 | logger.info(f"Fetching all alerts for index {index_alerts_search_body.index_name}") |
| 134 | return await get_index_alerts(index_alerts_search_body) |
| 135 | |
| 136 | |
| 137 | @wazuh_indexer_alerts_router.post( |
| 138 | "/hosts/all", |
| 139 | response_model=AlertsByHostResponse, |
| 140 | description="Get number of all alerts for all hosts", |
| 141 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 142 | ) |
| 143 | async def get_all_alerts_by_host( |
| 144 | alerts_search_body: AlertsSearchBody, |
| 145 | ) -> AlertsByHostResponse: |
| 146 | """ |
| 147 | Fetches the number of all alerts for all hosts. |
| 148 | |
| 149 | Args: |
| 150 | alerts_search_body (AlertsSearchBody): The search body containing the filters for the alerts. |
| 151 | |
| 152 | Returns: |
| 153 | AlertsByHostResponse: The response containing the number of alerts for each host. |
| 154 | """ |
| 155 | logger.info("Fetching number of all alerts for all hosts") |
| 156 | return await get_alerts_by_host(alerts_search_body) |
| 157 | |
| 158 | |
| 159 | @wazuh_indexer_alerts_router.post( |
| 160 | "/rules/all", |
| 161 | response_model=AlertsByRuleResponse, |
| 162 | description="Get number of all alerts for all rules", |
| 163 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 164 | ) |
| 165 | async def get_all_alerts_by_rule( |
| 166 | alerts_search_body: AlertsSearchBody, |
| 167 | ) -> AlertsByRuleResponse: |
| 168 | """ |
| 169 | Fetches the number of all alerts for all rules. |
| 170 | |
| 171 | Args: |
| 172 | alerts_search_body (AlertsSearchBody): The search body containing the filters for the alerts. |
| 173 | |
| 174 | Returns: |
| 175 | AlertsByRuleResponse: The response containing the number of alerts for each rule. |
| 176 | """ |
| 177 | logger.info("Fetching number of all alerts for all rules") |
| 178 | return await get_alerts_by_rule(alerts_search_body) |
| 179 | |
| 180 | |
| 181 | @wazuh_indexer_alerts_router.post( |
| 182 | "/rules/hosts/all", |
| 183 | response_model=AlertsByRulePerHostResponse, |
| 184 | description="Get number of all alerts for all rules per host", |
| 185 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 186 | ) |
| 187 | async def get_all_alerts_by_rule_per_host( |
| 188 | alerts_search_body: AlertsSearchBody, |
| 189 | ) -> AlertsByRulePerHostResponse: |
| 190 | """ |
| 191 | Get number of all alerts for all rules per host |
| 192 | |
| 193 | Args: |
| 194 | alerts_search_body (AlertsSearchBody): _description_ |
| 195 | |
| 196 | Returns: |
| 197 | AlertsByRulePerHostResponse: _description_ |
| 198 | """ |
| 199 | logger.info("Fetching number of all alerts for all rules per host") |
| 200 | return await get_alerts_by_rule_per_host(alerts_search_body) |
| 201 | |
| 202 | |
| 203 | @wazuh_indexer_alerts_router.post( |
| 204 | "/alerts/graylog", |
| 205 | response_model=AlertsSearchResponse, |
| 206 | description="Get alerts that are configured Via Graylog", |
| 207 | dependencies=[Security(AuthHandler().require_any_scope("admin", "analyst"))], |
| 208 | ) |
| 209 | async def get_alerts_not_created_in_copilot(request: GraylogAlertsSearchBody) -> AlertsSearchResponse: |
| 210 | """ |
| 211 | Get the Graylog event indices. Then get all the results from the list of indices, where `copilot_alert_id` does not exist. |
| 212 | """ |
| 213 | return AlertsSearchResponse(success=True, message="Alerts retrieved", alerts_summary=await get_graylog_alerts(request)) |