Exemple #1
0
 async def send_api_request(self):
     result = (await self.protocol.send_request(self.command,
                                                self.data)).decode()
     if result.startswith('Error'):
         raise exception.WazuhException(3009, result)
     elif result.startswith('WazuhException'):
         _, code, message = result.split(' ', 2)
         raise exception.WazuhException(int(code), message)
     elif result == 'There are no connected worker nodes':
         request_result = '{}'
     else:
         if self.command == b'dapi' or self.command == b'dapi_forward' or self.command == b'send_file' or \
                 result == 'Sent request to master node':
             try:
                 timeout = None if self.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.WazuhException(3020)
         else:
             request_result = result
     return request_result
Exemple #2
0
    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:
            raise exception.WazuhException(3012)
        except MemoryError:
            raise exception.WazuhException(1119)
        except Exception as e:
            raise exception.WazuhException(3009, str(e))
Exemple #3
0
 def get_api_response(self, in_command, future):
     result = future.result()
     if result.startswith(b'Error'):
         result = json.dumps(
             exception.WazuhException(3000,
                                      result.decode()).to_dict()).encode()
     elif result.startswith(b'WazuhException'):
         _, code, message = result.decode().split(' ', 2)
         result = json.dumps(
             exception.WazuhException(int(code),
                                      message).to_dict()).encode()
     asyncio.create_task(
         self.send_request(command=b'dapi_res'
                           if in_command == b'dapi' else b'control_res',
                           data=result))
Exemple #4
0
    def process_request(self, command: bytes, data: bytes):
        """
        Defines requests available in the local server

        :param command: Received command
        :param data: Received payload
        :return: A response
        """
        if command == b'dapi':
            self.server.dapi.add_request(self.name.encode() + b' ' + data)
            return b'ok', b'Added request to API requests queue'
        elif command == b'dapi_forward':
            node_name, request = data.split(b' ', 1)
            node_name = node_name.decode()
            if node_name == 'fw_all_nodes':
                if len(self.server.node.clients) > 0:
                    for node_name, node in self.server.node.clients.items():
                        asyncio.create_task(
                            node.send_request(
                                b'dapi',
                                self.name.encode() + b' ' + request))
                    return b'ok', b'Request forwarded to all worker nodes'
                else:
                    return b'ok', b'There are no connected worker nodes'
            elif node_name in self.server.node.clients:
                asyncio.create_task(
                    self.server.node.clients[node_name].send_request(
                        b'dapi',
                        self.name.encode() + b' ' + request))
                return b'ok', b'Request forwarded to worker node'
            else:
                raise exception.WazuhException(3022, node_name)
        else:
            return super().process_request(command, data)
Exemple #5
0
 def get_api_response(self, in_command, future):
     """
     Forwards response sent by the master to the local client. Callback of the send_request_to_master method.
     :param in_command: command originally sent to the master
     :param future: Request response
     :return: Nothing
     """
     result = future.result()
     if result.startswith(b'Error'):
         result = json.dumps(
             exception.WazuhException(3000,
                                      result.decode()).to_dict()).encode()
     elif result.startswith(b'WazuhException'):
         _, code, message = result.decode().split(' ', 2)
         result = json.dumps(
             exception.WazuhException(int(code),
                                      message).to_dict()).encode()
     asyncio.create_task(
         self.send_request(command=b'dapi_res'
                           if in_command == b'dapi' else b'control_res',
                           data=result))
Exemple #6
0
    async def distribute_function(self) -> str:
        """
        Distributes an API call

        :return: Dictionary with API response
        """
        try:
            request_type = rq.functions[self.input_json['function']]['type']
            is_dapi_enabled = self.cluster_items['distributed_api']['enabled']
            is_cluster_disabled = self.node == local_client and cluster.check_cluster_status(
            )

            if 'wait_for_complete' not in self.input_json['arguments']:
                self.input_json['arguments']['wait_for_complete'] = False

            # if it is a cluster API request and the cluster is not enabled, raise an exception
            if is_cluster_disabled and 'cluster' in self.input_json['function'] and \
                    self.input_json['function'] != '/cluster/status' and \
                    self.input_json['function'] != '/cluster/config' and \
                    self.input_json['function'] != '/cluster/node':
                raise exception.WazuhException(3013)

            # First case: execute the request local.
            # 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 request_type == 'local_any' or \
                    (request_type == 'local_master' and self.node_info['type'] == 'master') or \
                    (request_type == 'distributed_master' and self.input_json['from_cluster']):

                return 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 request_type == 'distributed_master' and self.node_info[
                    'type'] == 'master':
                return 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:
                return await self.execute_remote_request()
        except exception.WazuhException as e:
            if self.debug:
                raise
            return self.print_json(data=e.message, error=e.code)
        except Exception as e:
            if self.debug:
                raise
            return self.print_json(data=str(e), error=1000)
Exemple #7
0
    async def execute_local_request(self) -> str:
        """
        Executes an API request locally.

        :return: a JSON response.
        """
        def run_local(args):
            self.logger.debug("Starting to execute request locally")
            data = rq.functions[self.input_json['function']]['function'](
                **args)
            self.logger.debug("Finished executing request locally")
            return data

        try:
            before = time.time()

            self.check_wazuh_status(basic_services=rq.functions[
                self.input_json['function']].get('basic_services', None))

            timeout = None if self.input_json['arguments']['wait_for_complete'] \
                else self.cluster_items['intervals']['communication']['timeout_api_exe']
            local_args = copy.deepcopy(self.input_json['arguments'])
            del local_args[
                'wait_for_complete']  # local requests don't use this parameter

            if rq.functions[self.input_json['function']]['is_async']:
                task = run_local(local_args)
            else:
                loop = asyncio.get_running_loop()
                task = loop.run_in_executor(
                    None, functools.partial(run_local, local_args))

            try:
                data = await asyncio.wait_for(task, timeout=timeout)
            except asyncio.TimeoutError:
                raise exception.WazuhException(3021)

            after = time.time()
            self.logger.debug(
                "Time calculating request result: {}s".format(after - before))
            return self.print_json(data=data, error=0)
        except exception.WazuhException as e:
            if self.debug:
                raise
            return self.print_json(data=e.message, error=e.code)
        except Exception as e:
            self.logger.error(
                "Error executing API request locally: {}".format(e))
            if self.debug:
                raise
            return self.print_json(data=str(e), error=1000)
Exemple #8
0
    def check_wazuh_status(self):
        """
        There are some services that are required for wazuh to correctly process API requests. If any of those services
        is not running, the API must raise an exception indicating that:
            * It's not ready yet to process requests if services are restarting
            * There's an error in any of those services that must be adressed before using the API if any service is
              in failed status.
            * Wazuh must be started before using the API is the services are stopped.

        The basic services wazuh needs to be running are: wazuh-modulesd, ossec-remoted, ossec-analysisd, ossec-execd and wazuh-db
        """
        if self.input_json['function'] == '/manager/status' or self.input_json['function'] == '/cluster/:node_id/status':
            return
        basic_services = ('wazuh-modulesd', 'ossec-remoted', 'ossec-analysisd', 'ossec-execd', 'wazuh-db')
        status = operator.itemgetter(*basic_services)(manager.status())
        for status_name, exc_code in [('failed', 1019), ('restarting', 1017), ('stopped', 1018)]:
            if status_name in status:
                raise exception.WazuhException(exc_code, status)
Exemple #9
0
    def check_wazuh_status(self, basic_services=None):
        """
        There are some services that are required for wazuh to correctly process API requests. If any of those services
        is not running, the API must raise an exception indicating that:
            * It's not ready yet to process requests if services are restarting
            * There's an error in any of those services that must be adressed before using the API if any service is
              in failed status.
            * Wazuh must be started before using the API is the services are stopped.

        The basic services wazuh needs to be running are: wazuh-modulesd, ossec-remoted, ossec-analysisd, ossec-execd and wazuh-db
        """
        if self.input_json['function'] == '/manager/status' or self.input_json[
                'function'] == '/cluster/:node_id/status':
            return

        if not basic_services:
            basic_services = ('wazuh-modulesd', 'ossec-analysisd',
                              'ossec-execd', 'wazuh-db')
            if common.install_type != "local":
                basic_services += ('ossec-remoted', )

        status = manager.status()

        not_ready_daemons = {
            k: status[k]
            for k in basic_services
            if status[k] in ('failed', 'restarting', 'stopped')
        }

        if not_ready_daemons:
            extra_info = {
                'node_name':
                self.node_info.get('node', 'UNKNOWN NODE'),
                'not_ready_daemons':
                ', '.join([
                    f'{key}->{value}'
                    for key, value in not_ready_daemons.items()
                ])
            }
            raise exception.WazuhException(1017, extra_message=extra_info)
Exemple #10
0
 def process_request(self, command: bytes, data: bytes):
     if command == b'dapi':
         self.server.dapi.add_request(self.name.encode() + b' ' + data)
         return b'ok', b'Added request to API requests queue'
     elif command == b'dapi_forward':
         node_name, request = data.split(b' ', 1)
         node_name = node_name.decode()
         if node_name == 'fw_all_nodes':
             for node_name, node in self.server.node.clients.items():
                 asyncio.create_task(
                     node.send_request(b'dapi',
                                       self.name.encode() + b' ' + request))
             return b'ok', b'Request forwarded to all worker nodes'
         elif node_name in self.server.node.clients:
             asyncio.create_task(
                 self.server.node.clients[node_name].send_request(
                     b'dapi',
                     self.name.encode() + b' ' + request))
             return b'ok', b'Request forwarded to worker node'
         else:
             raise exception.WazuhException(3022, node_name)
     else:
         return super().process_request(command, data)
Exemple #11
0
    def get_connected_nodes(self,
                            filter_node=None,
                            offset=0,
                            limit=common.database_limit,
                            sort=None,
                            search=None,
                            select=None,
                            filter_type='all') -> Dict:
        """
        Return all connected nodes, including the master node
        :return: A dictionary containing data from each node
        """
        def return_node(node_info):
            return (filter_node is None or node_info['name']
                    in filter_node) and (filter_type == 'all'
                                         or node_info['type'] == filter_type)

        default_fields = self.to_dict()['info'].keys()
        if select is None:
            select = {'fields': default_fields}
        else:
            if not set(select['fields']).issubset(default_fields):
                raise exception.WazuhException(
                    1724, "Allowed fields: {}. Fields: {}".format(
                        ', '.join(default_fields),
                        ', '.join(set(select['fields']) - default_fields)))

        if filter_type != 'all' and filter_type not in {'worker', 'master'}:
            raise exception.WazuhException(
                1728, "Valid types are 'worker' and 'master'.")

        if filter_node is not None:
            filter_node = set(filter_node) if isinstance(
                filter_node, list) else {filter_node}
            if not filter_node.issubset(
                    set(
                        itertools.chain(self.clients.keys(),
                                        [self.configuration['node_name']]))):
                raise exception.WazuhException(1730)

        res = [
            val.to_dict()['info']
            for val in itertools.chain([self], self.clients.values())
            if return_node(val.to_dict()['info'])
        ]

        if sort is not None:
            res = utils.sort_array(array=res,
                                   sort_by=sort['fields'],
                                   order=sort['order'],
                                   allowed_sort_fields=default_fields)
        if search is not None:
            res = utils.search_array(array=res,
                                     text=search['value'],
                                     negation=search['negation'])

        return {
            'totalItems':
            len(res),
            'items':
            utils.cut_array([{k: v[k]
                              for k in select['fields']}
                             for v in res], offset, limit)
        }