Exemple #1
0
    def __init__(self, inventory_path, messages_path, tmp_path, time_step=0.5):
        """Create a new instance to monitor any given file in any specified host.

        Parameters
        ----------
        inventory_path : str
            Path to the hosts's inventory file.
        messages_path : str
            Path to the file where the callbacks, paths and hosts to be monitored are specified.
        tmp_path : str
            Path to the temporal files.
        time_step : float, optional
            Fraction of time to wait in every get. Default `0.5`.
        """
        self.host_manager = HostManager(inventory_path=inventory_path)
        self._queue = Manager().Queue()
        self._result = defaultdict(list)
        self._time_step = time_step
        self._file_monitors = list()
        self._monitored_files = set()
        self._file_content_collectors = list()
        self._tmp_path = tmp_path
        try:
            os.mkdir(self._tmp_path)
        except OSError:
            pass
        with open(messages_path, 'r') as f:
            self.test_cases = yaml.safe_load(f)
Exemple #2
0
def test_agent_key_polling(inventory_path):
    """Check that the agent key polling cycle works correctly. To do this, we use the messages and the hosts defined
    in data/messages.yml and the hosts inventory.

    Parameters
    ----------
    inventory_path : str
        Path to the Ansible hosts inventory
    """
    actual_path = os.path.dirname(os.path.abspath(__file__))
    host_manager = HostManager(inventory_path=inventory_path)
    configure_environment(host_manager)

    host_monitor = HostMonitor(inventory_path=inventory_path,
                               messages_path=os.path.join(
                                   actual_path, 'data/messages.yml'),
                               tmp_path=os.path.join(actual_path, 'tmp'))
    host_monitor.run()
Exemple #3
0
test_hosts = ['wazuh-master', 'wazuh-worker1', 'wazuh-worker2']
inventory_path = os.path.join(
    os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
    'provisioning', 'agentless_cluster', 'inventory.yml')
default_api_conf = os.path.join(os.path.dirname(os.path.abspath(__file__)),
                                'api_configurations', 'default.yaml')

# Testing credentials
test_user = None
test_passw = None
test_user_id = None
test_role_id = None
test_rule_id = None
test_policy_id = None

host_manager = HostManager(inventory_path)


@pytest.fixture(scope='module')
def set_role_to_user():
    """Create a relation between the testing user and role/policy."""
    token = host_manager.get_api_token(test_hosts[0])
    response = host_manager.make_api_call(
        test_hosts[0],
        method='POST',
        endpoint=
        f'/security/users/{test_user_id}/roles?role_ids={test_role_id}',
        token=token)
    assert response[
        'status'] == 200, f'Failed to set relation between user and role: {response}'
Exemple #4
0
class HostMonitor:
    """This class has the capability to monitor remote host. This monitoring consists of reading the specified files to
    check that the expected message arrives to them.

    If the goals are achieved, no exceptions will be raised and therefore the test will end properly and without
    failures.

    In contrast, if one or more of the goals is not covered, a timeout exception will be raised with a generic or a
    custom error message.
    """

    def __init__(self, inventory_path, messages_path, tmp_path, time_step=0.5):
        """Create a new instance to monitor any given file in any specified host.

        Parameters
        ----------
        inventory_path : str
            Path to the hosts's inventory file.
        messages_path : str
            Path to the file where the callbacks, paths and hosts to be monitored are specified.
        tmp_path : str
            Path to the temporal files.
        time_step : float, optional
            Fraction of time to wait in every get. Default `0.5`.
        """
        self.host_manager = HostManager(inventory_path=inventory_path)
        self._queue = Manager().Queue()
        self._result = defaultdict(list)
        self._time_step = time_step
        self._file_monitors = list()
        self._monitored_files = set()
        self._file_content_collectors = list()
        self._tmp_path = tmp_path
        try:
            os.mkdir(self._tmp_path)
        except OSError:
            pass
        with open(messages_path, 'r') as f:
            self.test_cases = yaml.safe_load(f)

    def run(self):
        """This method creates and destroy the needed processes for the messages founded in messages_path.
        It creates one file composer (process) for every file to be monitored in every host."""
        for host, payload in self.test_cases.items():
            self._monitored_files.update({case['path'] for case in payload})
            if len(self._monitored_files) == 0:
                raise AttributeError('There is no path to monitor. Exiting...')
            for path in self._monitored_files:
                output_path = f'{host}_{path.split("/")[-1]}.tmp'
                self._file_content_collectors.append(self.file_composer(host=host, path=path, output_path=output_path))
                logger.debug(f'Add new file composer process for {host} and path: {path}')
                self._file_monitors.append(self._start(host=host, payload=payload, path=output_path))
                logger.debug(f'Add new file monitor process for {host} and path: {path}')

        while True:
            if not any([handler.is_alive() for handler in self._file_monitors]):
                for handler in self._file_monitors:
                    handler.join()
                for file_collector in self._file_content_collectors:
                    file_collector.terminate()
                    file_collector.join()
                self.clean_tmp_files()
                break
            time.sleep(self._time_step)
        self.check_result()

    @new_process
    def file_composer(self, host, path, output_path):
        """Collects the file content of the specified path in the desired host and append it to the output_path file.
        Simulates the behavior of tail -f and redirect the output to output_path.

        Parameters
        ----------
        host : str
            Hostname.
        path : str
            Host file path to be collect.
        output_path : str
            Output path of the content collected from the remote host path.
        """
        try:
            truncate_file(os.path.join(self._tmp_path, output_path))
        except FileNotFoundError:
            pass
        logger.debug(f'Starting file composer for {host} and path: {path}. '
                     f'Composite file in {os.path.join(self._tmp_path, output_path)}')
        tmp_file = os.path.join(self._tmp_path, output_path)
        while True:
            with FileLock(tmp_file):
                with open(tmp_file, "r+") as file:
                    content = self.host_manager.get_file_content(host, path).split('\n')
                    file_content = file.read().split('\n')
                    for new_line in content:
                        if new_line == '':
                            continue
                        if new_line not in file_content:
                            file.write(f'{new_line}\n')
                time.sleep(self._time_step)

    @new_process
    def _start(self, host, payload, path, encoding=None, error_messages_per_host=None):
        """Start the file monitoring until the QueueMonitor returns an string or TimeoutError.

        Parameters
        ----------
        host : str
            Hostname
        payload : list of dict
            Contains the message to be found and the timeout for it.
        path : str
            Path where it must search for the message.
        encoding : str
            Encoding of the file.
        error_messages_per_host : dict
            Dictionary with hostnames as keys and desired error messages as values

        Returns
        -------
        instance of HostMonitor
        """
        tailer = FileTailer(os.path.join(self._tmp_path, path), time_step=self._time_step)
        try:
            if encoding is not None:
                tailer.encoding = encoding
            tailer.start()
            for case in payload:
                logger.debug(f'Starting QueueMonitor for {host} and message: {case["regex"]}')
                monitor = QueueMonitor(tailer.queue, time_step=self._time_step)
                try:
                    self._queue.put({host: monitor.start(timeout=case['timeout'],
                                                         callback=callback_generator(case['regex'])
                                                         ).result().strip('\n')})
                except TimeoutError:
                    try:
                        self._queue.put({host: error_messages_per_host[host]})
                    except (KeyError, TypeError):
                        self._queue.put({
                            host: TimeoutError(f'Did not found the expected callback in {host}: {case["regex"]}')})
                logger.debug(f'Finishing QueueMonitor for {host} and message: {case["regex"]}')
        finally:
            tailer.shutdown()

        return self

    def result(self):
        """Get the result of HostMonitor

        Returns
        -------
        dict
            Dict that contains the host as the key and a list of messages as the values
        """
        return self._result

    def check_result(self):
        """Check if a TimeoutError occurred."""
        logger.debug(f'Checking results...')
        while not self._queue.empty():
            result = self._queue.get(block=True)
            for host, msg in result.items():
                if isinstance(msg, TimeoutError):
                    raise msg
                logger.debug(f'Received from {host} the expected message: {msg}')
                self._result[host].append(msg)

    def clean_tmp_files(self):
        """Remove tmp files."""
        logger.debug(f'Cleaning temporal files...')
        for file in os.listdir(self._tmp_path):
            os.remove(os.path.join(self._tmp_path, file))