async def start(self): """ Connects to the server """ # Get a reference to the event loop as we plan to use # low-level APIs. asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) loop = asyncio.get_running_loop() on_con_lost = loop.create_future() try: self.transport, self.protocol = await loop.create_unix_connection( protocol_factory=lambda: LocalClientHandler( loop=loop, on_con_lost=on_con_lost, name=self.name, logger=self.logger, fernet_key='', manager=self, cluster_items=self.cluster_items), path='{}/queue/cluster/c-internal.sock'.format( common.ossec_path)) except (ConnectionRefusedError, FileNotFoundError): raise exception.WazuhInternalError(3012) except MemoryError: raise exception.WazuhInternalError(1119) except Exception as e: raise exception.WazuhInternalError(3009, str(e))
def test_execute_pagination(socket_send_mock, connect_mock): mywdb = WazuhDBConnection() # Test pagination with patch("wazuh.core.wdb.WazuhDBConnection._send", side_effect=[[{'total': 5}], exception.WazuhInternalError(2009), ['ok', '{"total": 5}'], ['ok', '{"total": 5}']]): mywdb.execute("agent 000 sql select test from test offset 1 limit 500") # Test pagination error with patch("wazuh.core.wdb.WazuhDBConnection._send", side_effect=[[{'total': 5}], exception.WazuhInternalError(2009)]): with pytest.raises(exception.WazuhInternalError, match=".* 2009 .*"): mywdb.execute("agent 000 sql select test from test offset 1 limit 1")
def dispatch(self, command: bytes, counter: int, payload: bytes) -> None: """Process a received message and send a response. Parameters ---------- command : bytes Command received. counter : int Message ID. payload : bytes Data received. """ try: command, payload = self.process_request(command, payload) except exception.WazuhException as e: self.logger.error( f"Internal error processing request '{command}': {e}") command, payload = b'err', json.dumps( e, cls=WazuhJSONEncoder).encode() except Exception as e: self.logger.error( f"Unhandled error processing request '{command}': {e}", exc_info=True) command, payload = b'err', json.dumps( exception.WazuhInternalError(1000, extra_message=str(e)), cls=WazuhJSONEncoder).encode() if command is not None: self.push(self.msg_build(command, counter, payload))
def __or__(self, other): """ | operator used to merge two AbstractWazuhResult objects. When merged with a WazuhException, the result is always a WazuhException :param other: AbstractWazuhResult or WazuhException :return: a new AbstractWazuhResult or WazuhException """ if isinstance(other, wexception.WazuhException): return other elif not isinstance(other, (dict, AbstractWazuhResult)): raise wexception.WazuhInternalError( 1000, extra_message=f"Cannot be merged with {type(other)} object") result = deepcopy(self) for key, field in other.items(): if key not in result: result[key] = field elif isinstance(field, dict): result[key] = self._merge_dict(result[key], field, key=key) elif isinstance(field, list): self_field = result[key] result[key] = self._merge_list(self_field, field, key=key) elif isinstance(field, Number): result[key] = self._merge_number(result[key], field, key=key) elif isinstance(field, str): # str result[key] = self._merge_str(result[key], field, key=key) return result
def __or__(self, other): result = super().__or__(other) if isinstance(other, wexception.WazuhError): if len(other.ids) > 0: for id_ in other.ids: self.add_failed_item(id_=id_, error=other) return self else: return other elif isinstance(result, wexception.WazuhException): return result elif not isinstance(other, AffectedItemsWazuhResult): raise wexception.WazuhInternalError( 1000, extra_message=f"Cannot be merged with {type(other)} object") result.add_failed_items_from(other) result.affected_items = merge(result.affected_items, other.affected_items, criteria=self.sort_fields, ascending=self.sort_ascending, types=self.sort_casting) result.total_affected_items = result.total_affected_items + other.total_affected_items return result
def as_wazuh_object(dct: Dict): try: if '__callable__' in dct: encoded_callable = dct['__callable__'] funcname = encoded_callable['__name__'] if '__wazuh__' in encoded_callable: # Encoded Wazuh instance method wazuh_dict = encoded_callable['__wazuh__'] wazuh = Wazuh() return getattr(wazuh, funcname) else: # Encoded function or static method qualname = encoded_callable['__qualname__'].split('.') classname = qualname[0] if len(qualname) > 1 else None module_path = encoded_callable['__module__'] module = import_module(module_path) if classname is None: return getattr(module, funcname) else: return getattr(getattr(module, classname), funcname) elif '__wazuh_exception__' in dct: wazuh_exception = dct['__wazuh_exception__'] return getattr(exception, wazuh_exception['__class__']).from_dict(wazuh_exception['__object__']) elif '__wazuh_result__' in dct: wazuh_result = dct['__wazuh_result__'] return getattr(wresults, wazuh_result['__class__']).decode_json(wazuh_result['__object__']) elif '__wazuh_datetime__' in dct: return datetime.datetime.fromisoformat(dct['__wazuh_datetime__']) return dct except (KeyError, AttributeError): raise exception.WazuhInternalError(1000, extra_message=f"Wazuh object cannot be decoded from JSON {dct}", cmd_error=True)
async def send_api_request(self, command: bytes, data: bytes, wait_for_complete: bool) -> str: """ Sends a command to the server and waits for the response :param command: Command to execute :param data: Payload :param wait_for_complete: Whether to enable timeout waiting for the response or not :return: Response from the server """ result = (await self.protocol.send_request(command, data)).decode() if result == 'There are no connected worker nodes': request_result = {} else: if command == b'dapi' or command == b'dapi_forward' or command == b'send_file' or \ result == 'Sent request to master node': try: timeout = None if wait_for_complete \ else self.cluster_items['intervals']['communication']['timeout_api_request'] await asyncio.wait_for( self.protocol.response_available.wait(), timeout=timeout) request_result = self.protocol.response.decode() except asyncio.TimeoutError: raise exception.WazuhInternalError(3020) else: request_result = result return request_result
def dispatch(self, command: bytes, counter: int, payload: bytes) -> None: """ Processes a received message and sends a response :param command: command received :param counter: message id :param payload: data received """ try: command, payload = self.process_request(command, payload) except exception.WazuhException as e: self.logger.error( "Internal error processing request '{}': {}".format( command, e)) command, payload = b'err', json.dumps( e, cls=WazuhJSONEncoder).encode() except Exception as e: self.logger.error( "Unhandled error processing request '{}': {}".format( command, e), exc_info=True) command, payload = b'err', json.dumps( exception.WazuhInternalError(1000, extra_message=str(e)), cls=WazuhJSONEncoder).encode() if command is not None: self.push(self.msg_build(command, counter, payload))
def add_failed_items_from(self, other): """Adds all failed items from other into the caller object :param other: AffectedItemsWazuhResult instance :return: """ if not isinstance(other, AffectedItemsWazuhResult): raise wexception.WazuhInternalError( 1000, extra_message= f"Failed items cannot be taken from {type(other)} object") for error, ids in other._failed_items.items(): for id_ in ids: self.add_failed_item(id_=id_, error=error)
def __init__(self, dct): """ Initializes an instance :param dct: map to take key-values from """ if isinstance(dct, dict): self.dikt = dct elif isinstance(dct, AbstractWazuhResult): self.dikt = dct.dikt else: raise wexception.WazuhInternalError( 1000, extra_message=f"dct param must be a dict or " f"an AbstractWazuhResult subclass, " f"not a {type(dct)}")
async def send_api_request(self, command: bytes, data: bytes, wait_for_complete: bool) -> str: """Send DAPI request to the server and wait for response. Parameters ---------- command : bytes Command to execute. data : bytes Data to send. wait_for_complete : bool Whether to raise a timeout exception or not. Returns ------- request_result : dict API response. """ result = (await self.protocol.send_request(command, data)).decode() if result == 'There are no connected worker nodes': request_result = {} else: # Wait for expected data if it is not returned by send_request(), # which occurs when the following commands are used. if command == b'dapi' or command == b'dapi_forward' or command == b'send_file' or command == b'sendasync' \ or result == 'Sent request to master node': try: timeout = None if wait_for_complete \ else self.cluster_items['intervals']['communication']['timeout_api_request'] await asyncio.wait_for( self.protocol.response_available.wait(), timeout=timeout) request_result = self.protocol.response.decode() except asyncio.TimeoutError: raise exception.WazuhInternalError(3020) # If no data is expected (only the send_request() result), immediately return the output of send_request. else: request_result = result return request_result
async def execute_local_request(self) -> str: """Execute an API request locally. Returns ------- str JSON response. """ def run_local(): self.debug_log("Starting to execute request locally") common.rbac.set(self.rbac_permissions) common.broadcast.set(self.broadcasting) common.cluster_nodes.set(self.nodes) common.current_user.set(self.current_user) common.origin_module.set(self.origin_module) data = self.f(**self.f_kwargs) common.reset_context_cache() self.debug_log("Finished executing request locally") return data try: if self.f_kwargs.get('agent_list') == '*': del self.f_kwargs['agent_list'] before = time.time() self.check_wazuh_status() timeout = self.api_request_timeout if not self.wait_for_complete else None # LocalClient only for control functions if self.local_client_arg is not None: lc = local_client.LocalClient() self.f_kwargs[self.local_client_arg] = lc try: if self.is_async: task = run_local() else: loop = asyncio.get_running_loop() task = loop.run_in_executor(threadpool, run_local) try: data = await asyncio.wait_for(task, timeout=timeout) except asyncio.TimeoutError: raise exception.WazuhInternalError(3021) except OperationalError: raise exception.WazuhInternalError(2008) except json.decoder.JSONDecodeError: raise exception.WazuhInternalError(3036) self.debug_log( f"Time calculating request result: {time.time() - before:.3f}s" ) return data except (exception.WazuhError, exception.WazuhResourceNotFound) as e: e.dapi_errors = self.get_error_info(e) if self.debug: raise return json.dumps(e, cls=c_common.WazuhJSONEncoder) except exception.WazuhInternalError as e: e.dapi_errors = self.get_error_info(e) # Avoid exception info if it is an asyncio timeout or JSONDecodeError self.logger.error(f"{e.message}", exc_info=e.code not in {3021, 3036}) if self.debug: raise return json.dumps(e, cls=c_common.WazuhJSONEncoder) except Exception as e: self.logger.error(f'Error executing API request locally: {str(e)}', exc_info=True) if self.debug: raise return json.dumps(exception.WazuhInternalError( 1000, dapi_errors=self.get_error_info(e)), cls=c_common.WazuhJSONEncoder)
async def distribute_function(self) -> [Dict, exception.WazuhException]: """ Distribute an API call. Returns ------- dict or WazuhException Dictionary with API response or WazuhException in case of error. """ try: if 'password' in self.f_kwargs: self.debug_log( f"Receiving parameters { {**self.f_kwargs, 'password': '******'} }" ) elif 'token_nbf_time' in self.f_kwargs: self.logger.debug(f"Decoded token {self.f_kwargs}") else: self.debug_log(f"Receiving parameters {self.f_kwargs}") is_dapi_enabled = self.cluster_items['distributed_api']['enabled'] is_cluster_disabled = self.node == local_client and not check_cluster_status( ) # First case: execute the request locally. # If the distributed api is not enabled # If the cluster is disabled or the request type is local_any # if the request was made in the master node and the request type is local_master # if the request came forwarded from the master node and its type is distributed_master if not is_dapi_enabled or is_cluster_disabled or self.request_type == 'local_any' or \ (self.request_type == 'local_master' and self.node_info['type'] == 'master') or \ (self.request_type == 'distributed_master' and self.from_cluster): response = await self.execute_local_request() # Second case: forward the request # Only the master node will forward a request, and it will only be forwarded if its type is distributed_ # master elif self.request_type == 'distributed_master' and self.node_info[ 'type'] == 'master': response = await self.forward_request() # Last case: execute the request remotely. # A request will only be executed remotely if it was made in a worker node and its type isn't local_any else: response = await self.execute_remote_request() try: response = json.loads(response, object_hook=c_common.as_wazuh_object) \ if isinstance(response, str) else response except json.decoder.JSONDecodeError: response = {'message': response} return response if isinstance(response, (wresults.AbstractWazuhResult, exception.WazuhException)) \ else wresults.WazuhResult(response) except json.decoder.JSONDecodeError: e = exception.WazuhInternalError(3036) e.dapi_errors = self.get_error_info(e) if self.debug: raise self.logger.error(f"{e.message}") return e except exception.WazuhError as e: e.dapi_errors = self.get_error_info(e) return e except exception.WazuhInternalError as e: e.dapi_errors = self.get_error_info(e) if self.debug: raise self.logger.error(f"{e.message}", exc_info=True) return e except Exception as e: if self.debug: raise self.logger.error(f'Unhandled exception: {str(e)}', exc_info=True) return exception.WazuhInternalError( 1000, dapi_errors=self.get_error_info(e))
async def execute_local_request(self) -> str: """Execute an API request locally. Returns ------- str JSON response. """ def run_local(): self.logger.debug("Starting to execute request locally") common.rbac.set(self.rbac_permissions) common.broadcast.set(self.broadcasting) common.cluster_nodes.set(self.nodes) common.current_user.set(self.current_user) data = self.f(**self.f_kwargs) common.reset_context_cache() self.logger.debug("Finished executing request locally") return data try: before = time.time() self.check_wazuh_status() timeout = None if self.wait_for_complete \ else self.cluster_items['intervals']['communication']['timeout_api_exe'] # LocalClient only for control functions if self.local_client_arg is not None: lc = local_client.LocalClient() self.f_kwargs[self.local_client_arg] = lc else: lc = None if self.is_async: task = run_local() else: loop = asyncio.get_running_loop() task = loop.run_in_executor(self.threadpool, run_local) try: data = await asyncio.wait_for(task, timeout=timeout) except asyncio.TimeoutError: raise exception.WazuhInternalError(3021) self.logger.debug( f"Time calculating request result: {time.time() - before}s") return data except (exception.WazuhError, exception.WazuhResourceNotFound) as e: e.dapi_errors = self.get_error_info(e) if self.debug: raise return json.dumps(e, cls=c_common.WazuhJSONEncoder) except exception.WazuhInternalError as e: e.dapi_errors = self.get_error_info(e) self.logger.error(f"{e.message}", exc_info=True) if self.debug: raise return json.dumps(e, cls=c_common.WazuhJSONEncoder) except Exception as e: self.logger.error(f'Error executing API request locally: {str(e)}', exc_info=True) if self.debug: raise return json.dumps(exception.WazuhInternalError( 1000, dapi_errors=self.get_error_info(e)), cls=c_common.WazuhJSONEncoder)
async def execute_local_request(self) -> str: """Execute an API request locally. Returns ------- str JSON response. """ try: if self.f_kwargs.get('agent_list') == '*': del self.f_kwargs['agent_list'] before = time.time() self.check_wazuh_status() timeout = self.api_request_timeout if not self.wait_for_complete else None # LocalClient only for control functions if self.local_client_arg is not None: lc = local_client.LocalClient() self.f_kwargs[self.local_client_arg] = lc try: if self.is_async: task = self.run_local(self.f, self.f_kwargs, self.logger, self.rbac_permissions, self.broadcasting, self.nodes, self.current_user) else: loop = asyncio.get_event_loop() if 'thread_pool' in pools: pool = pools.get('thread_pool') elif self.f.__name__ in authentication_funcs: pool = pools.get('authentication_pool') else: pool = pools.get('process_pool') task = loop.run_in_executor( pool, partial(self.run_local, self.f, self.f_kwargs, self.logger, self.rbac_permissions, self.broadcasting, self.nodes, self.current_user)) try: data = await asyncio.wait_for(task, timeout=timeout) except asyncio.TimeoutError: raise exception.WazuhInternalError(3021) except OperationalError: raise exception.WazuhInternalError(2008) except process.BrokenProcessPool: raise exception.WazuhInternalError(901) except json.decoder.JSONDecodeError: raise exception.WazuhInternalError(3036) except process.BrokenProcessPool: raise exception.WazuhInternalError(900) self.debug_log( f"Time calculating request result: {time.time() - before:.3f}s" ) return data except (exception.WazuhError, exception.WazuhResourceNotFound) as e: e.dapi_errors = self.get_error_info(e) if self.debug: raise return json.dumps(e, cls=c_common.WazuhJSONEncoder) except exception.WazuhInternalError as e: e.dapi_errors = self.get_error_info(e) # Avoid exception info if it is an asyncio timeout or JSONDecodeError self.logger.error(f"{e.message}", exc_info=e.code not in {3021, 3036}) if self.debug: raise return json.dumps(e, cls=c_common.WazuhJSONEncoder) except Exception as e: self.logger.error(f'Error executing API request locally: {str(e)}', exc_info=True) if self.debug: raise return json.dumps(exception.WazuhInternalError( 1000, dapi_errors=self.get_error_info(e)), cls=c_common.WazuhJSONEncoder)