Exemplo n.º 1
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
        """
        # modify logger filter tag in LocalServerHandlerWorker entry point
        context_tag.set("Local " + self.name)

        self.logger.debug2("Command received: {}".format(command))
        if command == b'dapi':
            if self.server.node.client is None:
                raise WazuhClusterError(3023)
            asyncio.create_task(self.server.node.client.send_request(b'dapi', self.name.encode() + b' ' + data))
            return b'ok', b'Added request to API requests queue'
        elif command == b'sendsync':
            if self.server.node.client is None:
                raise WazuhClusterError(3023)
            asyncio.create_task(self.server.node.client.send_request(b'sendsync', self.name.encode() + b' ' + data))
            return None, None
        elif command == b'sendasync':
            if self.server.node.client is None:
                raise WazuhClusterError(3023)
            asyncio.create_task(self.server.node.client.send_request(b'sendsync', self.name.encode() + b' ' + data))
            return b'ok', b'Added request to sendsync requests queue'
        else:
            return super().process_request(command, data)
Exemplo n.º 2
0
    def process_request(self, command: bytes,
                        data: bytes) -> Union[bytes, Tuple[bytes, bytes]]:
        """Define all commands that a worker can receive from the master.

        Parameters
        ----------
        command : bytes
            Received command.
        data : bytes
            Received payload.

        Returns
        -------
        bytes
            Result.
        bytes
            Response message.
        """
        self.logger.debug(f"Command received: '{command}'")
        if command == b'syn_m_c_ok':
            return self.sync_integrity_ok_from_master()
        elif command == b'syn_m_c':
            return self.setup_receive_files_from_master()
        elif command == b'syn_m_c_e':
            return self.end_receiving_integrity(data.decode())
        elif command == b'syn_m_c_r':
            return self.error_receiving_integrity(data.decode())
        elif command == b'syn_m_a_e':
            return self.sync_agent_info_from_master(data.decode())
        elif command == b'syn_m_a_err':
            return self.error_receiving_agent_info(data.decode())
        elif command == b'dapi_res':
            asyncio.create_task(self.forward_dapi_response(data))
            return b'ok', b'Response forwarded to worker'
        elif command == b'sendsyn_res':
            asyncio.create_task(self.forward_sendsync_response(data))
            return b'ok', b'Response forwarded to worker'
        elif command == b'dapi_err':
            dapi_client, error_msg = data.split(b' ', 1)
            try:
                asyncio.create_task(self.manager.local_server.clients[
                    dapi_client.decode()].send_request(command, error_msg))
            except WazuhClusterError as e:
                raise WazuhClusterError(3025)
            return b'ok', b'DAPI error forwarded to worker'
        elif command == b'sendsyn_err':
            sendsync_client, error_msg = data.split(b' ', 1)
            try:
                asyncio.create_task(self.manager.local_server.clients[
                    sendsync_client.decode()].send_request(b'err', error_msg))
            except WazuhClusterError as e:
                raise WazuhClusterError(3025)
            return b'ok', b'SendSync error forwarded to worker'
        elif command == b'dapi':
            self.manager.dapi.add_request(b'master*' + data)
            return b'ok', b'Added request to API requests queue'
        else:
            return super().process_request(command, data)
Exemplo n.º 3
0
    def process_request(self, command: bytes, data: bytes):
        """Define requests available in the local server.

        Parameters
        ----------
        command : bytes
            Received command from client.
        data : bytes
            Received command from client.

        Returns
        -------
        bytes
            Result.
        bytes
            Response message.
        """
        context_tag.set("Local " + self.name)

        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_fwd':
            node_name, request = data.split(b' ', 1)
            node_name = node_name.decode()
            if 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 WazuhClusterError(3022)
        else:
            return super().process_request(command, data)
Exemplo n.º 4
0
    def send_file_request(self, path, node_name):
        """Send a file from the API to the cluster.

        Used in API calls to update configuration or manager files.

        Parameters
        ----------
        path : str
            Path of the file to send.
        node_name : str
            Node name to send the file.

        Returns
        -------
        bytes
            Result.
        bytes
            Response message.
        """
        if node_name not in self.server.node.clients:
            raise WazuhClusterError(3022)
        else:
            req = asyncio.create_task(
                self.server.node.clients[node_name].send_file(path))
            req.add_done_callback(self.get_send_file_response)
            return b'ok', b'Forwarding file to master node'
Exemplo n.º 5
0
async def get_node(lc: local_client.LocalClient, filter_node=None, select=None):
    """Get basic information of one cluster node.

    Parameters
    ----------
    lc : LocalClient object
        LocalClient with which to send the 'get_nodes' request.
    filter_node : str, list
        Node to return.
    select : dict
        Select which fields to return (separated by comma).

    Returns
    -------
    result : dict
        Data of the node.
    """
    arguments = {'filter_node': filter_node, 'offset': 0, 'limit': common.database_limit, 'sort': None, 'search': None,
                 'select': select, 'filter_type': 'all'}

    response = await lc.execute(command=b'get_nodes', data=json.dumps(arguments).encode(),
                                wait_for_complete=False)
    try:
        node_info_array = json.loads(response, object_hook=as_wazuh_object)
    except json.JSONDecodeError as e:
        raise WazuhClusterError(3020) if 'timeout' in response else e

    if isinstance(node_info_array, Exception):
        raise node_info_array

    if len(node_info_array['items']) > 0:
        return node_info_array['items'][0]
    else:
        return {}
Exemplo n.º 6
0
    def send_request_to_master(self, command: bytes, arguments: bytes):
        """Forward a request to the master node.

        Parameters
        ----------
        command : bytes
            Command to forward.
        arguments : bytes
            Payload to forward.

        Returns
        -------
        bytes
            Result.
        bytes
            Response message.
        """
        if self.server.node.client is None:
            raise WazuhClusterError(3023)
        else:
            request = asyncio.create_task(
                self.server.node.client.send_request(command, arguments))
            request.add_done_callback(
                functools.partial(self.get_api_response, command))
            return b'ok', b'Sent request to master node'
Exemplo n.º 7
0
async def get_health(lc: local_client.LocalClient, filter_node=None):
    """Get nodes and synchronization information.

    Parameters
    ----------
    lc : LocalClient object
        LocalClient with which to send the 'get_nodes' request.
    filter_node : str, list
        Node to return.

    Returns
    -------
    result : dict
        Basic information of each node and synchronization process related information.
    """
    response = await lc.execute(command=b'get_health',
                                data=json.dumps(filter_node).encode(),
                                wait_for_complete=False)

    try:
        result = json.loads(response, object_hook=as_wazuh_object)
    except json.JSONDecodeError as e:
        raise WazuhClusterError(3020) if 'timeout' in response else e

    if isinstance(result, Exception):
        raise result

    return result
Exemplo n.º 8
0
 def process_request(self, command: bytes,
                     data: bytes) -> Tuple[bytes, bytes]:
     """
     Defines all commands that a worker can receive from the master
     :param command: Received command
     :param data: Payload received
     :return: A response
     """
     self.logger.debug("Command received: '{}'".format(command))
     if command == b'sync_m_c_ok':
         return self.sync_integrity_ok_from_master()
     elif command == b'sync_m_c':
         return self.setup_receive_files_from_master()
     elif command == b'sync_m_c_e':
         return self.end_receiving_integrity(data.decode())
     elif command == b'sync_m_c_r':
         return self.error_receiving_integrity(data.decode())
     elif command == b'dapi_res':
         asyncio.create_task(self.forward_dapi_response(data))
         return b'ok', b'Response forwarded to worker'
     elif command == b'dapi_err':
         dapi_client, error_msg = data.split(b' ', 1)
         try:
             asyncio.create_task(self.manager.local_server.clients[
                 dapi_client.decode()].send_request(command, error_msg))
         except WazuhClusterError as e:
             raise WazuhClusterError(3025)
         return b'ok', b'DAPI error forwarded to worker'
     elif command == b'dapi':
         self.manager.dapi.add_request(b'master*' + data)
         return b'ok', b'Added request to API requests queue'
     else:
         return super().process_request(command, data)
Exemplo n.º 9
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
        """
        #modify logger filter tag in LocalServerHandlerMaster entry point
        context_tag.set("Local " + self.name)

        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 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 WazuhClusterError(3022)
        else:
            return super().process_request(command, data)
Exemplo n.º 10
0
    def process_request(self, command: bytes, data: bytes):
        """Define available requests in the local server.

        Parameters
        ----------
        command : bytes
            Received command from client.
        data : bytes
            Received payload from client.

        Returns
        -------
        bytes
            Result.
        bytes
            Response message.
        """
        # Modify logger filter tag in LocalServerHandlerWorker entry point.
        context_tag.set("Local " + self.name)

        self.logger.debug2(f"Command received: {command}")
        if command == b'dapi':
            if self.server.node.client is None:
                raise WazuhClusterError(3023)
            asyncio.create_task(
                self.server.node.client.send_request(
                    b'dapi',
                    self.name.encode() + b' ' + data))
            return b'ok', b'Added request to API requests queue'
        elif command == b'sendsync':
            if self.server.node.client is None:
                raise WazuhClusterError(3023)
            asyncio.create_task(
                self.server.node.client.send_request(
                    b'sendsync',
                    self.name.encode() + b' ' + data))
            return None, None
        elif command == b'sendasync':
            if self.server.node.client is None:
                raise WazuhClusterError(3023)
            asyncio.create_task(
                self.server.node.client.send_request(
                    b'sendsync',
                    self.name.encode() + b' ' + data))
            return b'ok', b'Added request to sendsync requests queue'
        else:
            return super().process_request(command, data)
Exemplo n.º 11
0
    async def sync(self):
        """Start synchronization process with the master and send necessary information."""
        start_time = time.time()
        result = await self.worker.send_request(command=self.cmd+b'_p', data=b'')
        if isinstance(result, Exception):
            self.logger.error(f"Error asking for permission: {result}")
            return
        elif result == b'True':
            self.logger.debug("Permission to synchronize granted.")
        else:
            self.logger.info(f"Finished in {(time.time()-start_time):.3f}s. Master didn't grant permission to "
                             f"synchronize.")
            return

        # Start the synchronization process with the master and get a taskID.
        task_id = await self.worker.send_request(command=self.cmd, data=b'')
        if isinstance(task_id, Exception):
            raise task_id
        elif task_id.startswith(b'Error'):
            self.logger.error(task_id.decode())
            exc_info = json.dumps(exception.WazuhClusterError(code=3016, extra_message=str(task_id)),
                                  cls=c_common.WazuhJSONEncoder).encode()
            await self.worker.send_request(command=self.cmd + b'_r', data=b'None ' + exc_info)
            return

        self.logger.debug("Compressing files and 'files_metadata.json'." if self.files_to_sync else
                          "Compressing 'files_metadata.json'.")
        compressed_data_path = wazuh.core.cluster.cluster.compress_files(name=self.worker.name,
                                                                         list_path=self.files_to_sync,
                                                                         cluster_control_json=self.files_metadata)

        try:
            # Send zip file to the master into chunks.
            self.logger.debug("Sending zip file to master.")
            await self.worker.send_file(filename=compressed_data_path)
            self.logger.debug("Zip file sent to master.")

            # Finish the synchronization process and notify where the file corresponding to the taskID is located.
            result = await self.worker.send_request(command=self.cmd + b'_e', data=task_id + b' ' +
                                                    os.path.relpath(compressed_data_path, common.wazuh_path).encode())
            if isinstance(result, Exception):
                raise result
            elif result.startswith(b'Error'):
                raise WazuhClusterError(3016, extra_message=result.decode())
            return True
        except exception.WazuhException as e:
            # Notify error to master and delete its received file.
            self.logger.error(f"Error sending zip file: {e}")
            await self.worker.send_request(command=self.cmd+b'_r', data=task_id + b' ' +
                                           json.dumps(e, cls=c_common.WazuhJSONEncoder).encode())
        except Exception as e:
            # Notify error to master and delete its received file.
            self.logger.error(f"Error sending zip file: {e}")
            exc_info = json.dumps(exception.WazuhClusterError(code=1000, extra_message=str(e)),
                                  cls=c_common.WazuhJSONEncoder).encode()
            await self.worker.send_request(command=self.cmd+b'_r', data=task_id + b' ' + exc_info)
        finally:
            os.unlink(compressed_data_path)
Exemplo n.º 12
0
def test_DistributedAPI_tmp_file_cluster_error(mock_cluster_status):
    """Test the behaviour when an error raises with temporal files function."""
    open('/tmp/dapi_file.txt', 'a').close()
    with patch('wazuh.core.cluster.cluster.get_node', return_value={'type': 'master', 'node': 'unknown'}):
        with patch('wazuh.core.cluster.dapi.dapi.get_node_wrapper',
                   return_value=AffectedItemsWazuhResult(affected_items=[{'type': 'master', 'node': 'unknown'}])):
            with patch('wazuh.core.cluster.local_client.LocalClient.execute',
                       new=AsyncMock(side_effect=WazuhClusterError(3022))):
                dapi_kwargs = {'f': manager.status, 'logger': logger, 'request_type': 'distributed_master',
                               'f_kwargs': {'tmp_file': '/tmp/dapi_file.txt'}}
                raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=3022)

            open('/tmp/dapi_file.txt', 'a').close()
            with patch('wazuh.core.cluster.local_client.LocalClient.execute',
                       new=AsyncMock(side_effect=WazuhClusterError(1000))):
                dapi_kwargs = {'f': manager.status, 'logger': logger, 'request_type': 'distributed_master',
                               'f_kwargs': {'tmp_file': '/tmp/dapi_file.txt'}}
                raise_if_exc_routine(dapi_kwargs=dapi_kwargs, expected_error=1000)
Exemplo n.º 13
0
async def get_nodes(lc: local_client.LocalClient, filter_node=None, offset=0, limit=common.database_limit,
                    sort=None, search=None, select=None, filter_type='all', q=''):
    """Get basic information of each of the cluster nodes.

    Parameters
    ----------
    lc : LocalClient object
        LocalClient with which to send the 'get_nodes' request.
    filter_node : str, list
        Node to return.
    offset : int
        First element to return.
    limit : int
        Maximum number of elements to return.
    sort : dict
        Sort the collection by a field or fields.
    search : dict
        Look for elements with the specified string.
    select : dict
        Select which fields to return.
    filter_type : str
        Type of node (worker/master).
    q : str
        Query for filtering a list of results.

    Returns
    -------
    result : dict
        Data from each node.
    """
    if q:
        # If exists q parameter, apply limit and offset after filtering by q.
        arguments = {'filter_node': filter_node, 'offset': 0, 'limit': common.database_limit, 'sort': sort,
                     'search': search, 'select': select, 'filter_type': filter_type}
    else:
        arguments = {'filter_node': filter_node, 'offset': offset, 'limit': limit, 'sort': sort, 'search': search,
                     'select': select, 'filter_type': filter_type}
    response = await lc.execute(command=b'get_nodes',
                                data=json.dumps(arguments).encode(),
                                wait_for_complete=False)
    try:
        result = json.loads(response, object_hook=as_wazuh_object)
    except json.JSONDecodeError as e:
        raise WazuhClusterError(3020) if 'timeout' in response else e

    if isinstance(result, Exception):
        raise result

    if q:
        result['items'] = filter_array_by_query(q, result['items'])
        # Get totalItems after applying q filter.
        result['totalItems'] = len(result['items'])
        # Apply offset and limit filters.
        result['items'] = result['items'][offset:offset + limit]

    return result
Exemplo n.º 14
0
 def send_request_to_master(self, command: bytes, arguments: bytes):
     """
     Forwards a request to the master node.
     :param command: Command to forward
     :param arguments: Payload to forward
     :return: Confirmation message
     """
     if self.server.node.client is None:
         raise WazuhClusterError(3023)
     else:
         request = asyncio.create_task(self.server.node.client.send_request(command, arguments))
         request.add_done_callback(functools.partial(self.get_api_response, command))
         return b'ok', b'Sent request to master node'
Exemplo n.º 15
0
    async def sync(self):
        """
        Start synchronization process with the master and send necessary information
        """
        result = await self.worker.send_request(command=self.cmd+b'_p', data=b'')
        if isinstance(result, Exception):
            self.logger.error(f"Error asking for permission: {result}")
            return
        elif result == b'True':
            self.logger.info("Permission to synchronize granted")
        else:
            self.logger.info('Master didnt grant permission to synchronize')
            return

        task_id = await self.worker.send_request(command=self.cmd, data=b'')
        if isinstance(task_id, Exception):
            raise task_id
        elif task_id.startswith(b'Error'):
            self.logger.error(task_id.decode())
            exc_info = json.dumps(exception.WazuhClusterError(code=3016, extra_message=str(task_id)),
                                  cls=c_common.WazuhJSONEncoder).encode()
            await self.worker.send_request(command=self.cmd + b'_r', data=b'None ' + exc_info)
            return

        self.logger.info("Compressing files")
        compressed_data_path = wazuh.core.cluster.cluster.compress_files(name=self.worker.name,
                                                                         list_path=self.files_to_sync,
                                                                         cluster_control_json=self.checksums)

        try:
            self.logger.info("Sending compressed file to master")
            await self.worker.send_file(filename=compressed_data_path)
            self.logger.info("Worker files sent to master")
            result = await self.worker.send_request(command=self.cmd + b'_e', data=task_id + b' ' +
                                                    os.path.relpath(compressed_data_path, common.ossec_path).encode())
            if isinstance(result, Exception):
                raise result
            elif result.startswith(b'Error'):
                raise WazuhClusterError(3016, extra_message=result.decode())
        except exception.WazuhException as e:
            self.logger.error(f"Error sending files information: {e}")
            await self.worker.send_request(command=self.cmd+b'_r', data=task_id + b' ' +
                                           json.dumps(e, cls=c_common.WazuhJSONEncoder).encode())
        except Exception as e:
            self.logger.error(f"Error sending files information: {e}")
            exc_info = json.dumps(exception.WazuhClusterError(code=1000, extra_message=str(e)),
                                  cls=c_common.WazuhJSONEncoder).encode()
            await self.worker.send_request(command=self.cmd+b'_r', data=task_id + b' ' + exc_info)
        finally:
            os.unlink(compressed_data_path)
Exemplo n.º 16
0
    def send_file_request(self, path, node_name):
        """
        Sends a file from the API to the master who will send it to the specified cluster node.
        Used in API calls to update configuration or manager files.

        :param path: File to send
        :param node_name: node name to send the file
        :return: A response
        """
        if self.server.node.client is None:
            raise WazuhClusterError(3023)
        else:
            req = asyncio.create_task(self.server.node.client.send_file(path))
            req.add_done_callback(self.get_send_file_response)
            return b'ok', b'Forwarding file to master node'
Exemplo n.º 17
0
async def get_agents(lc: local_client.LocalClient, filter_node=None, filter_status=None):
    """Get list of agents and which node they are connected to.

    Parameters
    ----------
    lc : LocalClient object
        LocalClient with which to send the 'get_nodes' request.
    filter_node : list
        Node to return.
    filter_status : list
        Agent connection status to filter by.

    Returns
    -------
    result : dict
        Agent's basic information.
    """
    filter_status = ["all"] if not filter_status else filter_status
    filter_node = ["all"] if not filter_node else filter_node
    select_fields = {'id', 'ip', 'name', 'status', 'node_name', 'version'}

    input_json = {'f': Agent.get_agents_overview,
                  'f_kwargs': {
                      'filters': {'status': ','.join(filter_status), 'node_name': ','.join(filter_node)},
                      'limit': None,
                      'select': list(select_fields)
                  },
                  'from_cluster': False,
                  'wait_for_complete': False
                  }

    response = await lc.execute(command=b'dapi',
                                data=json.dumps(input_json, cls=WazuhJSONEncoder).encode(),
                                wait_for_complete=False)

    try:
        result = json.loads(response, object_hook=as_wazuh_object)
    except json.JSONDecodeError as e:
        raise WazuhClusterError(3020) if 'timeout' in response else e

    if isinstance(result, Exception):
        raise result
    # add unknown value to unfilled variables in result. For example, never_connected agents will miss the 'version'
    # variable.
    filled_result = [{**r, **{key: 'unknown' for key in select_fields - r.keys()}} for r in result['items']]
    result['items'] = filled_result
    return result
Exemplo n.º 18
0
    async def sync(self, start_time: float):
        """Start sending information to master node.

        Parameters
        ----------
        start_time : float
            Start time to be used when logging task duration if master's response is not expected.

        Returns
        -------
        bool
            True if data was correctly sent to the master node, None otherwise.
        """
        try:
            # Retrieve information from local wazuh-db
            get_chunks_start_time = time.time()
            chunks = self.data_retriever(self.get_data_command)
            self.logger.debug(
                f"Obtained {len(chunks)} chunks of data in {(time.time() - get_chunks_start_time):.3f}s."
            )
        except exception.WazuhException as e:
            self.logger.error(f"Error obtaining data from wazuh-db: {e}")
            return

        if chunks:
            # Send list of chunks as a JSON string
            data = json.dumps({
                "set_data_command": self.set_data_command,
                "chunks": chunks
            }).encode()
            task_id = await self.worker.send_string(data)
            if task_id.startswith(b'Error'):
                raise WazuhClusterError(
                    3016,
                    extra_message=
                    f'agent-info string could not be sent to the master '
                    f'node: {task_id}')

            # Specify under which task_id the JSON can be found in the master.
            await self.worker.send_request(command=self.cmd, data=task_id)
            self.logger.debug(f"All chunks sent.")
        else:
            self.logger.info(
                f"Finished in {(time.time() - start_time):.3f}s (0 chunks sent)."
            )
        return True
Exemplo n.º 19
0
    def send_file_request(self, path, node_name):
        """Send a file from the API to the master, which will forward it to the specified cluster node.

        Parameters
        ----------
        path : str
            Path of the file to send.
        node_name : str
            Node name to send the file.

        Returns
        -------
        bytes
            Result.
        bytes
            Response message.
        """
        if self.server.node.client is None:
            raise WazuhClusterError(3023)
        else:
            req = asyncio.create_task(self.server.node.client.send_file(path))
            req.add_done_callback(self.get_send_file_response)
            return b'ok', b'Forwarding file to master node'
Exemplo n.º 20
0
    async def process_files_from_master(self, name: str, file_received: asyncio.Event):
        """Perform relevant actions for each file according to its status.

        Process integrity files coming from the master. It updates necessary information and sends the master
        any required extra_valid files.

        Parameters
        ----------
        name : str
            Task ID that was waiting for the file to be received.
        file_received : asyncio.Event
            Asyncio event that is unlocked once the file has been received.
        """
        logger = self.task_loggers['Integrity sync']

        try:
            await asyncio.wait_for(file_received.wait(),
                                   timeout=self.cluster_items['intervals']['communication']['timeout_receiving_file'])
        except Exception:
            await self.send_request(
                command=b'syn_i_w_m_r',
                data=b'None ' + json.dumps(timeout_exc := WazuhClusterError(
                    3039, extra_message=f'Integrity sync at {self.name}'), cls=c_common.WazuhJSONEncoder).encode())
            raise timeout_exc

        if isinstance(self.sync_tasks[name].filename, Exception):
            exc_info = json.dumps(exception.WazuhClusterError(
                1000, extra_message=str(self.sync_tasks[name].filename)), cls=c_common.WazuhJSONEncoder)
            await self.send_request(command=b'syn_i_w_m_r', data=b'None ' + exc_info.encode())
            raise self.sync_tasks[name].filename

        zip_path = ""
        # Path of the zip containing a JSON with metadata and files to be updated in this worker node.
        received_filename = self.sync_tasks[name].filename

        try:
            self.integrity_sync_status['date_start'] = datetime.utcnow().timestamp()
            logger.info("Starting.")

            """
            - zip_path contains the path of the unzipped directory
            - ko_files contains a Dict with this structure:
              {'missing': {'<file_path>': {<MD5, merged, merged_name, etc>}, ...},
               'shared': {...}, 'extra': {...}, 'extra_valid': {...}}
            """
            ko_files, zip_path = await cluster.run_in_pool(self.loop, self.manager.task_pool, cluster.decompress_files,
                                                           received_filename)
            logger.info("Files to create: {} | Files to update: {} | Files to delete: {} | Files to send: {}".format(
                len(ko_files['missing']), len(ko_files['shared']), len(ko_files['extra']), len(ko_files['extra_valid']))
            )

            if ko_files['shared'] or ko_files['missing'] or ko_files['extra']:
                # Update or remove files in this worker node according to their status (missing, extra or shared).
                logger.debug("Worker does not meet integrity checks. Actions required.")
                logger.debug("Updating local files: Start.")
                await cluster.run_in_pool(self.loop, self.manager.task_pool, self.update_master_files_in_worker,
                                          ko_files, zip_path, self.cluster_items, self.task_loggers['Integrity sync'])
                logger.debug("Updating local files: End.")

            # Send extra valid files to the master.
            if ko_files['extra_valid']:
                logger.debug("Master requires some worker files.")
                asyncio.create_task(self.sync_extra_valid(ko_files['extra_valid']))
            else:
                logger.info(
                    f"Finished in {datetime.utcnow().timestamp() - self.integrity_sync_status['date_start']:.3f}s.")

        except exception.WazuhException as e:
            logger.error(f"Error synchronizing extra valid files: {e}")
            await self.send_request(command=b'syn_i_w_m_r',
                                    data=b'None ' + json.dumps(e, cls=c_common.WazuhJSONEncoder).encode())
        except Exception as e:
            logger.error(f"Error synchronizing extra valid files: {e}")
            exc_info = json.dumps(exception.WazuhClusterError(1000, extra_message=str(e)),
                                  cls=c_common.WazuhJSONEncoder)
            await self.send_request(command=b'syn_i_w_m_r', data=b'None ' + exc_info.encode())
        finally:
            zip_path and shutil.rmtree(zip_path)
Exemplo n.º 21
0
    async def sync(self, files_to_sync: Dict, files_metadata: Dict):
        """Send metadata and files to the master node.

        Parameters
        ----------
        files_to_sync : dict
            Paths (keys) and metadata (values) of the files to send to the master. Keys in this dictionary
            will be iterated to add the files they refer to the zip file that the master will receive.
        files_metadata : dict
            Paths (keys) and metadata (values) of the files to send to the master. This dict will be included as
            a JSON file named files_metadata.json.

        Returns
        -------
        bool
            True if files were correctly sent to the master node, None otherwise.
        """
        # Start the synchronization process with the master and get a taskID.
        task_id = await self.worker.send_request(command=self.cmd, data=b'')
        if isinstance(task_id, Exception):
            raise task_id
        elif task_id.startswith(b'Error'):
            self.logger.error(task_id.decode())
            exc_info = json.dumps(exception.WazuhClusterError(
                code=3016, extra_message=str(task_id)),
                                  cls=c_common.WazuhJSONEncoder).encode()
            await self.worker.send_request(command=self.cmd + b'_r',
                                           data=b'None ' + exc_info)
            return

        self.logger.debug(
            f"Compressing {'files and ' if files_to_sync else ''}'files_metadata.json' of {len(files_metadata)} files."
        )
        compressed_data_path = wazuh.core.cluster.cluster.compress_files(
            name=self.worker.name,
            list_path=files_to_sync,
            cluster_control_json=files_metadata)

        try:
            # Send zip file to the master into chunks.
            self.logger.debug("Sending zip file to master.")
            await self.worker.send_file(filename=compressed_data_path)
            self.logger.debug("Zip file sent to master.")

            # Finish the synchronization process and notify where the file corresponding to the taskID is located.
            result = await self.worker.send_request(
                command=self.cmd + b'_e',
                data=task_id + b' ' + os.path.relpath(
                    compressed_data_path, common.wazuh_path).encode())
            if isinstance(result, Exception):
                raise result
            elif result.startswith(b'Error'):
                raise WazuhClusterError(3016, extra_message=result.decode())
            return True
        except exception.WazuhException as e:
            # Notify error to master and delete its received file.
            self.logger.error(f"Error sending zip file: {e}")
            await self.worker.send_request(
                command=self.cmd + b'_r',
                data=task_id + b' ' +
                json.dumps(e, cls=c_common.WazuhJSONEncoder).encode())
        except Exception as e:
            # Notify error to master and delete its received file.
            self.logger.error(f"Error sending zip file: {e}")
            exc_info = json.dumps(exception.WazuhClusterError(
                code=1000, extra_message=str(e)),
                                  cls=c_common.WazuhJSONEncoder).encode()
            await self.worker.send_request(command=self.cmd + b'_r',
                                           data=task_id + b' ' + exc_info)
        finally:
            os.unlink(compressed_data_path)