| 1 | from typing import List |
| 2 | from typing import Tuple |
| 3 | |
| 4 | from fastapi import HTTPException |
| 5 | from loguru import logger |
| 6 | |
| 7 | from app.connectors.graylog.schema.collector import ConfiguredInput |
| 8 | from app.connectors.graylog.schema.collector import ConfiguredInputsResponse |
| 9 | from app.connectors.graylog.schema.collector import GraylogIndexItem |
| 10 | from app.connectors.graylog.schema.collector import GraylogIndicesResponse |
| 11 | from app.connectors.graylog.schema.collector import GraylogInputsResponse |
| 12 | from app.connectors.graylog.schema.collector import RunningInput |
| 13 | from app.connectors.graylog.schema.collector import RunningInputsResponse |
| 14 | from app.connectors.graylog.schema.management import UrlWhitelistEntryResponse |
| 15 | from app.connectors.graylog.utils.universal import send_get_request |
| 16 | |
| 17 | # async def get_indices_full() -> GraylogIndicesResponse: |
| 18 | # """Get indices from Graylog. |
| 19 | |
| 20 | # Returns: |
| 21 | # GraylogIndicesResponse: The response object containing the collected indices. |
| 22 | |
| 23 | # Raises: |
| 24 | # HTTPException: If there is an error collecting the indices. |
| 25 | # """ |
| 26 | # logger.info("Getting indices from Graylog") |
| 27 | # indices_collected = await send_get_request(endpoint="/api/system/indexer/indices") |
| 28 | # if indices_collected["success"]: |
| 29 | # try: |
| 30 | # indices_data = indices_collected["data"]["all"]["indices"] |
| 31 | # except KeyError: |
| 32 | # raise HTTPException(status_code=500, detail="Failed to collect indices key") |
| 33 | |
| 34 | # # Convert the dictionary to a list of GraylogIndexItem |
| 35 | # indices_list = [GraylogIndexItem(index_name=name, index_info=info) for name, info in indices_data.items()] |
| 36 | |
| 37 | # return GraylogIndicesResponse( |
| 38 | # indices=indices_list, |
| 39 | # success=True, |
| 40 | # message="Indices collected successfully", |
| 41 | # ) |
| 42 | # else: |
| 43 | # return GraylogIndicesResponse( |
| 44 | # indices=[], |
| 45 | # success=False, |
| 46 | # message="Failed to collect indices", |
| 47 | # ) |
| 48 | |
| 49 | |
| 50 | async def get_indices_full() -> GraylogIndicesResponse: |
| 51 | """Get indices from Graylog. |
| 52 | |
| 53 | Returns: |
| 54 | GraylogIndicesResponse: The response object containing the collected indices. |
| 55 | |
| 56 | Raises: |
| 57 | HTTPException: If there is an error collecting the indices. |
| 58 | """ |
| 59 | logger.info("Getting indices from Graylog") |
| 60 | indices_collected = await send_get_request(endpoint="/api/system/indexer/indices") |
| 61 | if indices_collected["success"]: |
| 62 | try: |
| 63 | indices_data = indices_collected["data"]["all"]["indices"] |
| 64 | except KeyError: |
| 65 | raise HTTPException(status_code=500, detail="Failed to collect indices key") |
| 66 | |
| 67 | # Check if indices_data is a dictionary or a list and process accordingly |
| 68 | if isinstance(indices_data, dict): |
| 69 | indices_list = [GraylogIndexItem(index_name=name, index_info=info) for name, info in indices_data.items()] |
| 70 | elif isinstance(indices_data, list): |
| 71 | indices_list = [GraylogIndexItem(index_name=index["index_name"], index_info=index) for index in indices_data] |
| 72 | else: |
| 73 | raise HTTPException(status_code=500, detail="Unexpected format for indices data") |
| 74 | |
| 75 | return GraylogIndicesResponse( |
| 76 | indices=indices_list, |
| 77 | success=True, |
| 78 | message="Indices collected successfully", |
| 79 | ) |
| 80 | else: |
| 81 | return GraylogIndicesResponse( |
| 82 | indices=[], |
| 83 | success=False, |
| 84 | message="Failed to collect indices", |
| 85 | ) |
| 86 | |
| 87 | |
| 88 | async def fetch_configured_inputs() -> Tuple[bool, List[ConfiguredInput]]: |
| 89 | """ |
| 90 | Fetches the configured inputs from the Graylog server. |
| 91 | |
| 92 | Returns: |
| 93 | A tuple containing a boolean indicating the success of the request and a list of ConfiguredInput objects. |
| 94 | """ |
| 95 | configured_inputs_collected = await send_get_request(endpoint="/api/system/inputs") |
| 96 | success = configured_inputs_collected.get("success", False) |
| 97 | |
| 98 | if success: |
| 99 | return True, [ConfiguredInput(**input_data) for input_data in configured_inputs_collected["data"]["inputs"]] |
| 100 | else: |
| 101 | logger.error("Failed to fetch configured inputs") |
| 102 | return False, [] |
| 103 | |
| 104 | |
| 105 | async def fetch_running_inputs() -> Tuple[bool, List[RunningInput]]: |
| 106 | """ |
| 107 | Fetches the running inputs from the Graylog API. |
| 108 | |
| 109 | Returns: |
| 110 | A tuple containing a boolean indicating the success of the request and a list of RunningInput objects. |
| 111 | """ |
| 112 | running_inputs_collected = await send_get_request( |
| 113 | endpoint="/api/system/inputstates", |
| 114 | ) |
| 115 | success = running_inputs_collected.get("success", False) |
| 116 | |
| 117 | if success: |
| 118 | return True, [RunningInput(**input_data) for input_data in running_inputs_collected["data"]["states"]] |
| 119 | else: |
| 120 | logger.error("Failed to fetch running inputs") |
| 121 | return False, [] |
| 122 | |
| 123 | |
| 124 | async def get_inputs() -> GraylogInputsResponse: |
| 125 | """Get inputs from Graylog. |
| 126 | |
| 127 | This function retrieves both configured and running inputs from Graylog. |
| 128 | It first fetches the configured inputs using the `fetch_configured_inputs` function, |
| 129 | and then fetches the running inputs using the `fetch_running_inputs` function. |
| 130 | If both fetch operations are successful, it returns a `GraylogInputsResponse` object |
| 131 | containing the configured and running inputs, along with a success message. |
| 132 | If either of the fetch operations fails, it returns a `GraylogInputsResponse` object |
| 133 | with empty input lists and a failure message. |
| 134 | |
| 135 | Returns: |
| 136 | GraylogInputsResponse: An object containing the configured and running inputs, |
| 137 | along with a success or failure message. |
| 138 | """ |
| 139 | logger.info("Getting inputs from Graylog") |
| 140 | |
| 141 | config_success, configured_inputs_list = await fetch_configured_inputs() |
| 142 | run_success, running_inputs_list = await fetch_running_inputs() |
| 143 | |
| 144 | if config_success and run_success: |
| 145 | logger.info("Successfully fetched both configured and running inputs") |
| 146 | return GraylogInputsResponse( |
| 147 | configured_inputs=configured_inputs_list, |
| 148 | running_inputs=running_inputs_list, |
| 149 | success=True, |
| 150 | message="Successfully retrieved inputs", |
| 151 | ) |
| 152 | else: |
| 153 | logger.error("Failed to fetch one or both types of inputs") |
| 154 | return GraylogInputsResponse( |
| 155 | configured_inputs=[], |
| 156 | running_inputs=[], |
| 157 | success=False, |
| 158 | message="Failed to collect inputs", |
| 159 | ) |
| 160 | |
| 161 | |
| 162 | async def get_inputs_running() -> RunningInputsResponse: |
| 163 | """Get running inputs from Graylog. |
| 164 | |
| 165 | Returns: |
| 166 | RunningInputsResponse: The response object containing the running inputs, success status, and message. |
| 167 | """ |
| 168 | logger.info("Getting running inputs from Graylog") |
| 169 | run_success, running_inputs_list = await fetch_running_inputs() |
| 170 | if run_success: |
| 171 | return RunningInputsResponse( |
| 172 | running_inputs=running_inputs_list, |
| 173 | success=True, |
| 174 | message="Successfully retrieved running inputs", |
| 175 | ) |
| 176 | |
| 177 | |
| 178 | async def get_inputs_configured() -> ConfiguredInputsResponse: |
| 179 | """Get configured inputs from Graylog. |
| 180 | |
| 181 | Returns: |
| 182 | ConfiguredInputsResponse: The response object containing the configured inputs, success status, and message. |
| 183 | """ |
| 184 | logger.info("Getting configured inputs from Graylog") |
| 185 | config_success, configured_inputs_list = await fetch_configured_inputs() |
| 186 | if config_success: |
| 187 | return ConfiguredInputsResponse( |
| 188 | configured_inputs=configured_inputs_list, |
| 189 | success=True, |
| 190 | message="Successfully retrieved configured inputs", |
| 191 | ) |
| 192 | |
| 193 | |
| 194 | async def get_index_names() -> List[str]: |
| 195 | """ |
| 196 | Gets the names of all the indices in Graylog. |
| 197 | |
| 198 | Returns: |
| 199 | List[str]: A list of all the index names. |
| 200 | """ |
| 201 | logger.info("Getting index names from Graylog") |
| 202 | |
| 203 | indices_collected = await get_indices_full() |
| 204 | |
| 205 | if indices_collected.success: |
| 206 | # Access the index_name attribute directly |
| 207 | return [index.index_name for index in indices_collected.indices] |
| 208 | else: |
| 209 | return [] |
| 210 | |
| 211 | |
| 212 | async def get_input_ids() -> List[str]: |
| 213 | """ |
| 214 | Gets the IDs of all the inputs in Graylog. |
| 215 | |
| 216 | Returns: |
| 217 | List[str]: A list of all the input IDs. |
| 218 | """ |
| 219 | logger.info("Getting input IDs from Graylog") |
| 220 | |
| 221 | success, inputs_collected = await fetch_configured_inputs() |
| 222 | |
| 223 | if success: |
| 224 | # Access the input_id attribute directly |
| 225 | return [input.id for input in inputs_collected] |
| 226 | else: |
| 227 | return [] |
| 228 | |
| 229 | |
| 230 | async def get_url_whitelist_entries() -> UrlWhitelistEntryResponse: |
| 231 | """ |
| 232 | Retrieves the URL whitelist entries from Graylog. |
| 233 | |
| 234 | Returns: |
| 235 | UrlWhitelistEntryResponse: The response object containing the URL whitelist entries. |
| 236 | """ |
| 237 | logger.info("Getting URL whitelist entries from Graylog") |
| 238 | # Graylog 7.0 renamed urlwhitelist -> urlallowlist. Try the 7.x path first and |
| 239 | # fall back to the 6.x path so both server versions are supported. GET is |
| 240 | # idempotent, so the fallback is safe. |
| 241 | try: |
| 242 | response = await send_get_request(endpoint="/api/system/urlallowlist") |
| 243 | except HTTPException: |
| 244 | logger.info("urlallowlist endpoint unavailable, falling back to legacy urlwhitelist (Graylog < 7.0)") |
| 245 | response = await send_get_request(endpoint="/api/system/urlwhitelist") |
| 246 | logger.info(f"URL whitelist entries response: {response}") |
| 247 | if response["success"]: |
| 248 | try: |
| 249 | url_whitelist_entries = response["data"] |
| 250 | except KeyError: |
| 251 | raise HTTPException( |
| 252 | status_code=500, |
| 253 | detail="Failed to collect URL whitelist entries", |
| 254 | ) |
| 255 | return UrlWhitelistEntryResponse( |
| 256 | url_whitelist_entries=url_whitelist_entries, |
| 257 | success=True, |
| 258 | message="URL whitelist entries collected successfully", |
| 259 | ) |
| 260 | else: |
| 261 | return UrlWhitelistEntryResponse( |
| 262 | url_whitelist_entries=[], |
| 263 | success=False, |
| 264 | message="Failed to collect URL whitelist entries", |
| 265 | ) |
| 266 | |
| 267 | |
| 268 | async def get_stream_id_by_stream_name(stream_name: str) -> str: |
| 269 | """Get stream ID from Graylog by stream name. |
| 270 | |
| 271 | Args: |
| 272 | stream_name (str): The name of the stream. |
| 273 | |
| 274 | Returns: |
| 275 | str: The ID of the stream. |
| 276 | |
| 277 | Raises: |
| 278 | HTTPException: If there is an error collecting the stream ID. |
| 279 | """ |
| 280 | logger.info(f"Getting stream ID from Graylog for stream {stream_name}") |
| 281 | streams_collected = await send_get_request(endpoint="/api/streams") |
| 282 | try: |
| 283 | if streams_collected["success"]: |
| 284 | for stream in streams_collected["data"]["streams"]: |
| 285 | if stream["title"] == stream_name: |
| 286 | return stream["id"] |
| 287 | else: |
| 288 | return "" |
| 289 | except KeyError as e: |
| 290 | logger.error(f"Failed to collect stream ID key: {e}") |
| 291 | raise HTTPException( |
| 292 | status_code=500, |
| 293 | detail=f"Failed to collect stream ID key: {e}", |
| 294 | ) |
| 295 | |
| 296 | |
| 297 | async def get_input_id_by_input_name(input_name: str) -> str: |
| 298 | """Get input ID from Graylog by input name. |
| 299 | |
| 300 | Args: |
| 301 | input_name (str): The name of the input. |
| 302 | |
| 303 | Returns: |
| 304 | str: The ID of the input. |
| 305 | |
| 306 | Raises: |
| 307 | HTTPException: If there is an error collecting the input ID. |
| 308 | """ |
| 309 | logger.info(f"Getting input ID from Graylog for input {input_name}") |
| 310 | inputs_collected = await send_get_request(endpoint="/api/system/inputs") |
| 311 | try: |
| 312 | if inputs_collected["success"]: |
| 313 | for input in inputs_collected["data"]["inputs"]: |
| 314 | if input["title"] == input_name: |
| 315 | return input["id"] |
| 316 | else: |
| 317 | return "" |
| 318 | except KeyError as e: |
| 319 | logger.error(f"Failed to collect input ID key: {e}") |
| 320 | raise HTTPException( |
| 321 | status_code=500, |
| 322 | detail=f"Failed to collect input ID key: {e}", |
| 323 | ) |
| 324 | |
| 325 | |
| 326 | async def get_content_pack_id_by_content_pack_name(content_pack_name: str) -> str: |
| 327 | """Get content pack ID from Graylog by content pack name. |
| 328 | |
| 329 | Args: |
| 330 | content_pack_name (str): The name of the content pack. |
| 331 | |
| 332 | Returns: |
| 333 | str: The ID of the content pack. |
| 334 | |
| 335 | Raises: |
| 336 | HTTPException: If there is an error collecting the content pack ID. |
| 337 | """ |
| 338 | logger.info(f"Getting content pack ID from Graylog for content pack {content_pack_name}") |
| 339 | content_packs_collected = await send_get_request(endpoint="/api/system/content_packs") |
| 340 | try: |
| 341 | if content_packs_collected["success"]: |
| 342 | for content_pack in content_packs_collected["data"]["content_packs"]: |
| 343 | if content_pack["name"] == content_pack_name: |
| 344 | return content_pack["id"] |
| 345 | else: |
| 346 | return "" |
| 347 | except KeyError as e: |
| 348 | logger.error(f"Failed to collect content pack ID key: {e}") |
| 349 | raise HTTPException( |
| 350 | status_code=500, |
| 351 | detail=f"Failed to collect content pack ID key: {e}", |
| 352 | ) |