def wait_mtime(path, time_step=5, timeout=-1): """ Wait until the monitored log is not being modified. Args: path (str): Path to the file. time_step (int, optional): Time step between checks of mtime. Default `5` timeout (int, optional): Timeout for function to fail. Default `-1` Raises: FileNotFoundError: Raised when the file does not exist. TimeoutError: Raised when timeout is reached. """ if not os.path.exists(path): raise FileNotFoundError(f"{path} not found.") last_mtime = 0.0 tic = datetime.now().timestamp() while last_mtime != os.path.getmtime(path): last_mtime = os.path.getmtime(path) time.sleep(time_step) if last_mtime - tic >= timeout: logger.error( f"{len(open(path, 'r').readlines())} lines within the file.") raise TimeoutError("Reached timeout.")
def test_symbolic_change_target_inside_folder(tags_to_apply, previous_target, new_target, get_configuration, configure_environment, restart_syscheckd, wait_for_fim_start): """Check if syscheck stops detecting events from previous target when pointing to a new folder Having a symbolic link pointing to a file/folder, change its target to another file/folder inside a monitored folder. After symlink_checker runs check that no events for the previous target file are detected while events for the new target are still being raised. Args: tags_to_apply (set): Run test if matches with a configuration identifier, skip otherwise. previous_target (str): Previous symlink target. new_target (str): New symlink target (path). get_configuration (fixture): Gets the current configuration of the test. configure_environment (fixture): Configure the environment for the execution of the test. restart_syscheckd (fixture): Restarts syscheck. wait_for_fim_start (fixture): Waits until the first FIM scan is completed. Raises: TimeoutError: If a expected event wasn't triggered. AttributeError: If a unexpected event was captured. ValueError: If the event's type and path are not the expected. """ check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' whodata = get_configuration['metadata']['fim_mode'] == 'whodata' file1 = 'regular1' symlink = 'symlink' if tags_to_apply == {'monitored_file'} else 'symlink2' # Check create event if it's pointing to a directory if tags_to_apply == {'monitored_dir'}: fim.create_file(fim.REGULAR, previous_target, file1, content='') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event') # Change the target to another file and wait the symcheck to update the link information modify_symlink(new_target, os.path.join(testdir_link, symlink)) wait_for_symlink_check(wazuh_log_monitor) fim.wait_for_audit(whodata, wazuh_log_monitor) # Modify the content of the previous target and don't expect events. Modify the new target and expect an event fim.modify_file_content(previous_target, file1, new_content='Sample modification') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') fim.modify_file_content(testdir2, file1, new_content='Sample modification') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) modify = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected ' '"Sending FIM event: ..." event').result() assert 'modified' in modify['data']['type'] and os.path.join(testdir2, file1) in modify['data']['path'], \ f"'modified' event not matching for {testdir2} {file1}"
def test_symbolic_delete_symlink(tags_to_apply, main_folder, aux_folder, get_configuration, configure_environment, restart_syscheckd, wait_for_initial_scan): """ Check if syscheck stops detecting events when deleting the monitored symlink. CHECK: Having a symbolic link pointing to a file/folder, remove that symbolic link file, wait for the symlink checker runs and modify the target file. No events should be detected. Restore the symbolic link and modify the target file again once symlink checker runs. Events should be detected now. Parameters ---------- main_folder : str Directory that is being pointed at or contains the pointed file. aux_folder : str Directory that will be pointed at or will contain the future pointed file. """ check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' file1 = 'regular1' if tags_to_apply == {'monitored_dir'}: create_file(REGULAR, main_folder, file1, content='') check_time_travel(scheduled, monitor=wazuh_log_monitor) wazuh_log_monitor.start( timeout=3, callback=callback_detect_event, error_message= 'Did not receive expected "Sending FIM event: ..." event') # Remove symlink and don't expect events symlink = 'symlink' if tags_to_apply == {'monitored_file'} else 'symlink2' delete_f(testdir_link, symlink) wait_for_symlink_check(wazuh_log_monitor) modify_file_content(main_folder, file1, new_content='Sample modification') check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') # Restore symlink and modify the target again. Expect events now create_file(SYMLINK, testdir_link, symlink, target=os.path.join(main_folder, file1)) wait_for_symlink_check(wazuh_log_monitor) modify_file_content(main_folder, file1, new_content='Sample modification 2') check_time_travel(scheduled, monitor=wazuh_log_monitor) modify = wazuh_log_monitor.start(timeout=3, callback=callback_detect_event).result() assert 'modified' in modify['data']['type'] and file1 in modify['data']['path'], \ f"'modified' event not matching for {file1}"
def test_symbolic_revert_symlink(tags_to_apply, get_configuration, configure_environment, restart_syscheckd, wait_for_initial_scan): """ Check if syscheck detects new targets properly CHECK: Having a symbolic link pointing to a file/folder, change its target to a folder. Check that the old file is not being monitored anymore and the new folder is. Revert the target change and ensure the file is being monitored and the folder is not. """ def modify_and_assert(file): modify_file_content(testdir1, file, new_content='Sample modification') check_time_travel(scheduled, monitor=wazuh_log_monitor) ev = wazuh_log_monitor.start(timeout=3, callback=callback_detect_event).result() assert 'modified' in ev['data']['type'] and os.path.join(testdir1, file) in ev['data']['path'], \ f"'modified' event not matching for {testdir1} {file}" check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' whodata = get_configuration['metadata']['fim_mode'] == 'whodata' file1 = 'regular1' file2 = 'regular2' # Don't expect an event since it is not being monitored yet modify_file_content(testdir1, file2, new_content='Sample modification') check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') # Change the target to the folder and now expect an event modify_symlink(testdir1, os.path.join(testdir_link, 'symlink')) wait_for_symlink_check(wazuh_log_monitor) wait_for_audit(whodata, wazuh_log_monitor) modify_and_assert(file2) # Modify symlink target, wait for sym_check to update it modify_symlink(os.path.join(testdir1, file1), os.path.join(testdir_link, 'symlink')) wait_for_symlink_check(wazuh_log_monitor) modify_file_content(testdir1, file2, new_content='Sample modification2') check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') modify_and_assert(file1)
def test_symbolic_change_target_inside_folder(tags_to_apply, previous_target, new_target, get_configuration, configure_environment, restart_syscheckd, wait_for_initial_scan): """ Check if syscheck stops detecting events from previous target when pointing to a new folder CHECK: Having a symbolic link pointing to a file/folder, change its target to another file/folder inside a monitored folder. After symlink_checker runs check that no events for the previous target file are detected while events for the new target are still being raised. Parameters ---------- previous_target : str Previous symlink target (path) new_target : str New symlink target (path). """ check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' whodata = get_configuration['metadata']['fim_mode'] == 'whodata' file1 = 'regular1' symlink = 'symlink' if tags_to_apply == {'monitored_file'} else 'symlink2' # Check create event if it's pointing to a directory if tags_to_apply == {'monitored_dir'}: create_file(REGULAR, previous_target, file1, content='') check_time_travel(scheduled, monitor=wazuh_log_monitor) wazuh_log_monitor.start(timeout=3, callback=callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event') # Change the target to another file and wait the symcheck to update the link information modify_symlink(new_target, os.path.join(testdir_link, symlink)) wait_for_symlink_check(wazuh_log_monitor) wait_for_audit(whodata, wazuh_log_monitor) # Modify the content of the previous target and don't expect events. Modify the new target and expect an event modify_file_content(previous_target, file1, new_content='Sample modification') check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') modify_file_content(testdir2, file1, new_content='Sample modification') check_time_travel(scheduled, monitor=wazuh_log_monitor) modify = wazuh_log_monitor.start(timeout=3, callback=callback_detect_event, error_message='Did not receive expected ' '"Sending FIM event: ..." event').result() assert 'modified' in modify['data']['type'] and os.path.join(testdir2, file1) in modify['data']['path'], \ f"'modified' event not matching for {testdir2} {file1}"
def modify_and_check_events(f1, f2, text): """ Modify the content of 2 given files. We assume the first one is being monitored and the other one is not. We expect a 'modified' event for the first one and a timeout for the second one. """ fim.modify_file_content(f1, file1, text) fim.modify_file_content(f2, file1, text) fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) modify = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event' ).result() assert 'modified' in modify['data']['type'] and f1 in modify['data']['path'], \ f"'modified' event not matching for {file1}" with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}')
def start(self, timeout=-1, callback=_callback_default, accum_results=1, update_position=True, timeout_extra=0, error_message=''): """Start the queue monitoring until the stop method is called.""" if not self._continue: self._continue = True self._abort = False result = None while self._continue: if self._abort: self.stop() if error_message: logger.error(error_message) logger.error( f"Results accumulated: " f"{len(result) if isinstance(result, list) else 0}" ) logger.error(f"Results expected: {accum_results}") raise TimeoutError(error_message) result = self.get_results(callback=callback, accum_results=accum_results, timeout=timeout, update_position=update_position, timeout_extra=timeout_extra) if result and not self._abort: self._result = result if self._result: self.stop() return self
def _monitor(self, callback=_callback_default, accum_results=1, update_position=True, timeout_extra=0, encoding=None, error_message=''): """Wait for new lines to be appended to the file. A callback function will be called every time a new line is detected. This function must receive two positional parameters: a references to the FileMonitor object and the line detected. """ previous_position = self._position if sys.platform == 'win32': encoding = None if encoding is None else encoding elif encoding is None: encoding = 'utf-8' self.extra_timer_is_running = False self._result = [] if accum_results > 1 or timeout_extra > 0 else None with open(self.file_path, encoding=encoding, errors='backslashreplace') as f: f.seek(self._position) while self._continue: if self._abort and not self.extra_timer_is_running: self.stop() if not isinstance(self._result, list) or accum_results != len(self._result): if error_message: logger.error(error_message) logger.error(f"Results accumulated: " f"{len(self._result) if isinstance(self._result, list) else 0}") logger.error(f"Results expected: {accum_results}") raise TimeoutError() self._position = f.tell() line = f.readline() if not line: f.seek(self._position) time.sleep(self.time_step) else: result = callback(line) if result: if type(self._result) == list: self._result.append(result) if accum_results == len(self._result): if timeout_extra > 0 and not self.extra_timer_is_running: self.extra_timer = Timer(timeout_extra, self.stop) self.extra_timer.start() self.extra_timer_is_running = True elif timeout_extra == 0: self.stop() else: self._result = result if self._result: self.stop() self._position = f.tell() if update_position else previous_position
def test_follow_symbolic_disabled(path, tags_to_apply, get_configuration, configure_environment, restart_syscheckd, wait_for_fim_start): """Check what happens when follow_symbolic_link option is set to "no". Ensure that the monitored symbolic link is considered a regular file and it will not follow its target path. It will only generate events if it changes somehow, not its target (file or directory) Args: path (str): Path of the target file or directory get_configuration (fixture): Gets the current configuration of the test. configure_environment (fixture): Configure the environment for the execution of the test. restart_syscheckd (fixture): Restarts syscheck. wait_for_fim_start (fixture): Waits until the first FIM scan is completed. Raises: TimeoutError: If a expected event wasn't triggered. AttributeError: If a unexpected event was captured. """ check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' regular_file = 'regular1' error_msg = 'A "Sending FIM event: ..." event has been detected. No events should be detected at this time.' # If the symlink targets to a directory, create a file in it and ensure no event is raised. if tags_to_apply == {'monitored_dir'}: fim.create_file(fim.REGULAR, path, regular_file) fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): wazuh_log_monitor.start(timeout=5, callback=fim.callback_detect_event) logger.error(error_msg) raise AttributeError(error_msg) # Modify the target file and don't expect any events fim.modify_file(path, regular_file, new_content='Modify sample') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): wazuh_log_monitor.start(timeout=5, callback=fim.callback_detect_event) logger.error(error_msg) raise AttributeError(error_msg) # Delete the target file and don't expect any events fim.delete_file(path, regular_file) fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): wazuh_log_monitor.start(timeout=5, callback=fim.callback_detect_event) logger.error(error_msg) raise AttributeError(error_msg)
def test_follow_symbolic_disabled(path, tags_to_apply, get_configuration, configure_environment, restart_syscheckd, wait_for_initial_scan): """Check what happens when follow_symbolic_link option is set to "no". Ensure that the monitored symbolic link is considered a regular file and it will not follow its target path. It will only generate events if it changes somehow, not its target (file or directory) Parameters ---------- path : str Path of the target file or directory """ check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' regular_file = 'regular1' error_msg = 'A "Sending FIM event: ..." event has been detected. No events should be detected at this time.' # If the symlink targets to a directory, create a file in it and ensure no event is raised. if tags_to_apply == {'monitored_dir'}: create_file(REGULAR, path, regular_file) check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): wazuh_log_monitor.start(timeout=5, callback=callback_detect_event) logger.error(error_msg) raise AttributeError(error_msg) # Modify the target file and don't expect any events modify_file(path, regular_file, new_content='Modify sample') check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): wazuh_log_monitor.start(timeout=5, callback=callback_detect_event) logger.error(error_msg) raise AttributeError(error_msg) # Delete the target file and don't expect any events delete_file(path, regular_file) check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): wazuh_log_monitor.start(timeout=5, callback=callback_detect_event) logger.error(error_msg) raise AttributeError(error_msg)
def test_symbolic_revert_symlink(tags_to_apply, get_configuration, configure_environment, restart_syscheckd, wait_for_fim_start): """Check if syscheck detects new targets properly Having a symbolic link pointing to a file/folder, change its target to a folder. Check that the old file is not being monitored anymore and the new folder is. Revert the target change and ensure the file is being monitored and the folder is not. Args: tags_to_apply (set): Run test if matches with a configuration identifier, skip otherwise. get_configuration (fixture): Gets the current configuration of the test. configure_environment (fixture): Configure the environment for the execution of the test. restart_syscheckd (fixture): Restarts syscheck. wait_for_fim_start (fixture): Waits until the first FIM scan is completed. Raises: TimeoutError: If a expected event wasn't triggered. AttributeError: If a unexpected event was captured. ValueError: If the event's type and path are not the expected. """ def modify_and_assert(file): fim.modify_file_content(testdir1, file, new_content='Sample modification') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) ev = wazuh_log_monitor.start( timeout=3, callback=fim.callback_detect_event).result() assert 'modified' in ev['data']['type'] and os.path.join(testdir1, file) in ev['data']['path'], \ f"'modified' event not matching for {testdir1} {file}" check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' whodata = get_configuration['metadata']['fim_mode'] == 'whodata' file1 = 'regular1' file2 = 'regular2' # Don't expect an event since it is not being monitored yet fim.modify_file_content(testdir1, file2, new_content='Sample modification') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') # Change the target to the folder and now expect an event modify_symlink(testdir1, os.path.join(testdir_link, 'symlink')) wait_for_symlink_check(wazuh_log_monitor) fim.wait_for_audit(whodata, wazuh_log_monitor) modify_and_assert(file2) # Modify symlink target, wait for sym_check to update it modify_symlink(os.path.join(testdir1, file1), os.path.join(testdir_link, 'symlink')) wait_for_symlink_check(wazuh_log_monitor) # Wait for audit to reload the rules fim.wait_for_audit(whodata, wazuh_log_monitor) fim.modify_file_content(testdir1, file2, new_content='Sample modification2') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') modify_and_assert(file1)
def test_symbolic_monitor_directory_with_symlink( monitored_dir, non_monitored_dir1, non_monitored_dir2, sym_target, tags_to_apply, get_configuration, configure_environment, clean_directories, restart_syscheckd, wait_for_initial_scan): """ Check what happens with a symlink and its target when syscheck monitors a directory with a symlink and not the symlink itself. When this happens, the symbolic link is considered a regular file and it will not follow its target path. It will only generate events if it changes somehow, not its target (file or directory) Parameters ---------- monitored_dir : str Monitored directory. non_monitored_dir1 : str Non-monitored directory. non_monitored_dir2 : str Non-monitored directory. """ check_apply_test(tags_to_apply, get_configuration['tags']) name1 = 'regular1' name2 = 'regular2' sl_name = 'symlink' a_path = os.path.join(non_monitored_dir1, name1) b_path = os.path.join( non_monitored_dir1, name2) if sym_target == 'file' else non_monitored_dir2 sl_path = os.path.join(monitored_dir, sl_name) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' # Create regular files out of the monitored directory and don't expect its event create_file(REGULAR, non_monitored_dir1, name1, content='') create_file(REGULAR, non_monitored_dir1, name2, content='') target = a_path if sym_target == 'file' else non_monitored_dir1 create_file(SYMLINK, monitored_dir, sl_name, target=target) # Create the syslink and expect its event, since it's withing the monitored directory check_time_travel(scheduled, monitor=wazuh_log_monitor) wazuh_log_monitor.start( timeout=global_parameters.default_timeout, callback=callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event' ) # Modify the target file and don't expect any event modify_file(non_monitored_dir1, name1, new_content='Modify sample') check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=5, callback=callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') # Modify the target of the symlink and expect the modify event modify_symlink(target=b_path, path=sl_path) check_time_travel(scheduled, monitor=wazuh_log_monitor) result = wazuh_log_monitor.start( timeout=global_parameters.default_timeout, callback=callback_detect_event, error_message='Did not receive expected ' '"Sending FIM event: ..." event').result() assert 'modified' in result['data'][ 'type'], f"No 'modified' event when modifying symlink" # Remove and restore the target file. Don't expect any events delete_file(b_path, name2) create_file(REGULAR, non_monitored_dir1, name2, content='') check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=5, callback=callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}')
def test_symbolic_monitor_directory_with_symlink( monitored_dir, non_monitored_dir1, non_monitored_dir2, sym_target, tags_to_apply, get_configuration, configure_environment, restart_syscheckd, wait_for_fim_start): """Check what happens with a symlink and its target when syscheck monitors a directory with a symlink and not the symlink itself. When this happens, the symbolic link is considered a regular file and it will not follow its target path. It will only generate events if it changes somehow, not its target (file or directory) Args: monitored_dir (str): Monitored directory. non_monitored_dir1 (str): Non-monitored directory. non_monitored_dir2 (str): Non-monitored directory. tags_to_apply (set): Run test if matches with a configuration identifier, skip otherwise. get_configuration (fixture): Gets the current configuration of the test. configure_environment (fixture): Configure the environment for the execution of the test. restart_syscheckd (fixture): Restarts syscheck. wait_for_fim_start (fixture): Waits until the first FIM scan is completed. Raises: TimeoutError: If a expected event wasn't triggered. AttributeError: If a unexpected event was captured. ValueError: If the event's type and path are not the expected. """ check_apply_test(tags_to_apply, get_configuration['tags']) name1 = f'{sym_target}regular1' name2 = f'{sym_target}regular2' sl_name = f'{sym_target}symlink' a_path = os.path.join(non_monitored_dir1, name1) b_path = os.path.join( non_monitored_dir1, name2) if sym_target == 'file' else non_monitored_dir2 sl_path = os.path.join(monitored_dir, sl_name) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' # Create regular files out of the monitored directory and don't expect its event fim.create_file(fim.REGULAR, non_monitored_dir1, name1, content='') fim.create_file(fim.REGULAR, non_monitored_dir1, name2, content='') target = a_path if sym_target == 'file' else non_monitored_dir1 fim.create_file(fim.SYMLINK, monitored_dir, sl_name, target=target) # Create the syslink and expect its event, since it's withing the monitored directory fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) wazuh_log_monitor.start( timeout=global_parameters.default_timeout, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event' ) # Modify the target file and don't expect any event fim.modify_file(non_monitored_dir1, name1, new_content='Modify sample') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=5, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') # Modify the target of the symlink and expect the modify event modify_symlink(target=b_path, path=sl_path) fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) result = wazuh_log_monitor.start( timeout=global_parameters.default_timeout, callback=fim.callback_detect_event, error_message='Did not receive expected ' '"Sending FIM event: ..." event').result() if 'modified' in result['data']['type']: logger.info( "Received modified event. No more events will be expected.") elif 'deleted' in result['data']['type']: logger.info( "Received deleted event. Now an added event will be expected.") result = wazuh_log_monitor.start( timeout=global_parameters.default_timeout, callback=fim.callback_detect_event, error_message='Did not receive expected ' '"Sending FIM event: ..." event').result() assert 'added' in result['data'][ 'type'], f"The event {result} should be of type 'added'" else: assert False, f"Detected event {result} should be of type 'modified' or 'deleted'" # Remove and restore the target file. Don't expect any events fim.delete_file(b_path, name2) fim.create_file(fim.REGULAR, non_monitored_dir1, name2, content='') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=5, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}')
def test_symbolic_delete_symlink(tags_to_apply, main_folder, aux_folder, get_configuration, configure_environment, restart_syscheckd, wait_for_fim_start): """Check if syscheck stops detecting events when deleting the monitored symlink. Having a symbolic link pointing to a file/folder, remove that symbolic link file, wait for the symlink checker runs and modify the target file. No events should be detected. Restore the symbolic link and modify the target file again once symlink checker runs. Events should be detected now. Args: tags_to_apply (set): Run test if matches with a configuration identifier, skip otherwise. main_folder (str): Directory that is being pointed at or contains the pointed file. aux_folder (str): Directory that will be pointed at or will contain the future pointed file. get_configuration (fixture): Gets the current configuration of the test. configure_environment (fixture): Configure the environment for the execution of the test. restart_syscheckd (fixture): Restarts syscheck. wait_for_fim_start (fixture): Waits until the first FIM scan is completed. Raises: TimeoutError: If a expected event wasn't triggered. AttributeError: If a unexpected event was captured. ValueError: If the event's type and path are not the expected. """ check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' file1 = 'regular1' if tags_to_apply == {'monitored_dir'}: fim.create_file(fim.REGULAR, main_folder, file1, content='') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) wazuh_log_monitor.start( timeout=3, callback=fim.callback_detect_event, error_message= 'Did not receive expected "Sending FIM event: ..." event') # Remove symlink and don't expect events symlink = 'symlink' if tags_to_apply == {'monitored_file'} else 'symlink2' delete_f(testdir_link, symlink) wait_for_symlink_check(wazuh_log_monitor) fim.modify_file_content(main_folder, file1, new_content='Sample modification') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') # Restore symlink and modify the target again. Expect events now fim.create_file(fim.SYMLINK, testdir_link, symlink, target=os.path.join(main_folder, file1)) wait_for_symlink_check(wazuh_log_monitor) # Wait unitl the audit rule of the link's target is loaded again fim.wait_for_audit(get_configuration['metadata']['fim_mode'] == "whodata", wazuh_log_monitor) fim.modify_file_content(main_folder, file1, new_content='Sample modification 2') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) modify = wazuh_log_monitor.start( timeout=3, callback=fim.callback_detect_event).result() assert 'modified' in modify['data']['type'] and file1 in modify['data']['path'], \ f"'modified' event not matching for {file1}"
help='Specify how many modified events will be expected. Default 4080.', action='store') parser.add_argument('-d', '--debug', dest='debug_level', default='ERROR', choices=list_of_choices, help='Specify debug level. Default "ERROR".', action='store') return parser.parse_args() if __name__ == '__main__': log_level = {'DEBUG': 10, 'ERROR': 40} options = get_script_arguments() events = options.n_events modified = options.dropped_events logger.setLevel(log_level[options.debug_level]) try: mitm = generate_analysisd_yaml(n_events=events, modify_events=modified) mitm.shutdown() except (TimeoutError, FileNotFoundError): logger.error( 'Could not generate the YAML. Please clean the environment.') delete_sockets() finally: kill_daemons() control_service('start')
def validate_analysis_alert_complex(alert, event, schema='linux'): """Check if an Analysis alert is properly formatted in reference to its Syscheck event. Args: alert (dict): Dictionary that represents an alert event (dict): Dictionary that represents an event event (dict): Dictionary that represent an event schema (str, optional): String with the schema to apply. Default `linux` """ def validate_attributes(syscheck_alert, syscheck_event, event_field, suffix): for attribute, value in syscheck_event['data'][event_field].items(): # Skip certain attributes since their alerts will not have them if attribute in ['type', 'checksum', 'attributes', 'value_type'] or ('inode' in attribute and schema == 'win32'): continue # Change `mtime` format to match with alerts elif attribute == 'mtime': value = datetime.utcfromtimestamp(value).isoformat() # Remove `hash_` from hash attributes since alerts do not have them elif 'hash' in attribute: attribute = attribute.split('_')[-1] # `perm` attribute has a different format on Windows elif 'perm' in attribute and schema == 'win32': if 'registry_key' in str(syscheck_event): continue attribute = 'win_perm' win_perm_list = [] for win_perm in value.split(','): user, effect, permissions = re.match(r'^(.+?) \((.+?)\): (.+?)$', win_perm).groups() win_perm_list.append({'name': user.strip(' '), effect: permissions.upper().split('|')}) value = win_perm_list if 'registry_key' in str(syscheck_event) and attribute in ['group_name', 'mtime']: continue attribute = '{}name'.format(attribute[0]) if attribute in ['user_name', 'group_name'] else attribute assert str(value) == str(syscheck_alert['{}_{}'.format(attribute, suffix)]), \ f"{value} not equal to {syscheck_alert['{}_{}'.format(attribute, suffix)]}" if 'tags' in event['data']: assert event['data']['tags'] == syscheck_alert['tags'][0], 'Tags not in alert or with different value' if 'content_changes' in event['data']: assert event['data']['content_changes'] == syscheck_alert['diff'] try: validate_analysis_alert(alert, schema) except exceptions.ValidationError as e: logger.error(f'Validation Error with: {alert}') raise e try: validate_attributes(deepcopy(alert['syscheck']), deepcopy(event), 'attributes', 'after') if event['data']['type'] == 'modified' and 'registry' not in str(event): validate_attributes(deepcopy(alert['syscheck']), deepcopy(event), 'old_attributes', 'before') except KeyError: raise KeyError('Alert does not have the same keys as the event.') # Full log validation: # Check that if the path is too long, it is displayed correctly. if len(event['data']['path']) > 756: full_log = alert['full_log'] file_name = event['data']['path'].rsplit('/', 1)[1] # Separation token that marks the part of the path that is lost assert '[...]' in full_log # File name is displayed correctly. assert file_name in full_log
def test_symbolic_delete_target(tags_to_apply, main_folder, aux_folder, get_configuration, configure_environment, restart_syscheckd, wait_for_fim_start): """Check if syscheck detects events properly when removing a target, have the symlink updated and then recreating the target Having a symbolic link pointing to a file/folder, remove that file/folder and check that deleted event is detected. Once symlink_checker runs create the same file. No events should be raised. Wait again for symlink_checker run and modify the file. Modification event must be detected this time. Args: tags_to_apply (set): Run test if matches with a configuration identifier, skip otherwise. main_folder (str): Directory that is being pointed at or contains the pointed file. aux_folder (str): Directory that will be pointed at or will contain the future pointed file. get_configuration (fixture): Gets the current configuration of the test. configure_environment (fixture): Configure the environment for the execution of the test. restart_syscheckd (fixture): Restarts syscheck. wait_for_fim_start (fixture): Waits until the first FIM scan is completed. Raises: TimeoutError: If a expected event wasn't triggered. AttributeError: If a unexpected event was captured. ValueError: If the event's type and path are not the expected. """ check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' whodata = get_configuration['metadata']['fim_mode'] == 'whodata' file1 = 'regular1' RELOAD_RULES_INTERVAL = 30 # If symlink is pointing to a directory, we need to add files and expect their 'added' event (only if the file # is being created withing the pointed directory. Then, delete the pointed file or directory if tags_to_apply == {'monitored_dir'}: fim.create_file(fim.REGULAR, main_folder, file1, content='') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event') delete_f(main_folder) else: delete_f(main_folder, file1) fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) delete = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event').result() assert 'deleted' in delete['data']['type'] and file1 in delete['data']['path'], \ f"'deleted' event not matching for {file1}" if tags_to_apply == {'monitored_dir'} and whodata: wazuh_log_monitor.start(timeout=3, callback=fim.callback_audit_removed_rule, error_message='Did not receive expected "Monitored directory \'{main_folder}\' was' 'removed: Audit rule removed') os.makedirs(main_folder, exist_ok=True, mode=0o777) wazuh_log_monitor.start(timeout=RELOAD_RULES_INTERVAL, callback=fim.callback_audit_reloading_rules, error_message='Did not receive expected "Reloading Audit rules" event') wazuh_log_monitor.start(timeout=RELOAD_RULES_INTERVAL, callback=fim.callback_audit_added_rule, error_message='Did not receive expected "Added audit rule... ' '\'{main_folder}\'" event') else: # If syscheck is monitoring with whodata, wait for audit to reload rules fim.wait_for_audit(whodata, wazuh_log_monitor) wait_for_symlink_check(wazuh_log_monitor) # Restore the target fim.create_file(fim.REGULAR, main_folder, file1, content='') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) if tags_to_apply == {'monitored_dir'} and whodata: wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event') else: # We don't expect any event since symlink hasn't updated the link information with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error('A "Sending FIM event: ..." event has been detected. No event should be detected as symlink ' 'has not updated the link information yet.') logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') wait_for_symlink_check(wazuh_log_monitor) fim.wait_for_audit(whodata, wazuh_log_monitor) # Modify the files and expect events since symcheck has updated now fim.modify_file_content(main_folder, file1, 'Sample modification') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) modify = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event').result() assert 'modified' in modify['data']['type'] and file1 in modify['data']['path'], \ f"'modified' event not matching for {file1}"
def validate_analysis_alert_complex(alert, event, schema='linux'): """Check if an Analysis alert is properly formatted in reference to its Syscheck event. Parameters ---------- alert : dict Dictionary that represents an alert event : dict Dictionary that represents an event event : dict Dictionary that represent an event schema : str, optional String with the schema to apply. Default `linux` """ def validate_attributes(syscheck_alert, syscheck_event, event_field, suffix): for attribute, value in syscheck_event['data'][event_field].items(): # Skip certain attributes since their alerts will not have them if attribute in ['type', 'checksum', 'attributes' ] or ('inode' in attribute and schema == 'win32'): continue # Change `mtime` format to match with alerts elif attribute == 'mtime': value = datetime.utcfromtimestamp(value).isoformat() # Remove `hash_` from hash attributes since alerts do not have them elif 'hash' in attribute: attribute = attribute.split('_')[-1] # `perm` attribute has a different format on Windows elif 'perm' in attribute and schema == 'win32': attribute = 'win_perm' win_perm_list = [] for win_perm in value.split(','): user, effect, permissions = re.match( r'^(.+?) \((.+?)\): (.+?)$', win_perm).groups() win_perm_list.append({ 'name': user.strip(' '), effect: permissions.upper().split('|') }) value = win_perm_list attribute = '{}name'.format(attribute[0]) if attribute in [ 'user_name', 'group_name' ] else attribute assert str(value) == str(syscheck_alert['{}_{}'.format(attribute, suffix)]), \ f"{value} not equal to {syscheck_alert['{}_{}'.format(attribute, suffix)]}" if 'tags' in event['data']: assert event['data']['tags'] == syscheck_alert['tags'][ 0], f'Tags not in alert or with different value' if 'content_changes' in event['data']: assert event['data']['content_changes'] == syscheck_alert['diff'] try: validate_analysis_alert(alert, schema) except exceptions.ValidationError as e: logger.error(f'Validation Error with: {alert}') raise e try: validate_attributes(deepcopy(alert['syscheck']), deepcopy(event), 'attributes', 'after') if event['data']['type'] == 'modified': validate_attributes(deepcopy(alert['syscheck']), deepcopy(event), 'old_attributes', 'before') except KeyError: raise KeyError('Alert does not have the same keys as the event.')
def test_symbolic_change_target(tags_to_apply, main_folder, aux_folder, get_configuration, configure_environment, restart_syscheckd, wait_for_fim_start): """Check if syscheck updates the symlink target properly Having a symbolic link pointing to a file/folder, change the target of the link to another file/folder. Ensure that the old file is being monitored and the new one is not before symlink_checker runs. Wait until symlink_checker runs and ensure that the new file is being monitored and the old one is not. Args: tags_to_apply (set): Run test if matches with a configuration identifier, skip otherwise. main_folder (str): Directory that is being pointed at or contains the pointed file. aux_folder (str): Directory that will be pointed at or will contain the future pointed file. get_configuration (fixture): Gets the current configuration of the test. configure_environment (fixture): Configure the environment for the execution of the test. restart_syscheckd (fixture): Restarts syscheck. wait_for_fim_start (fixture): Waits until the first FIM scan is completed. Raises: TimeoutError: If a expected event wasn't triggered. AttributeError: If a unexpected event was captured. """ def modify_and_check_events(f1, f2, text): """ Modify the content of 2 given files. We assume the first one is being monitored and the other one is not. We expect a 'modified' event for the first one and a timeout for the second one. """ fim.modify_file_content(f1, file1, text) fim.modify_file_content(f2, file1, text) fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) modify = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event' ).result() assert 'modified' in modify['data']['type'] and f1 in modify['data']['path'], \ f"'modified' event not matching for {file1}" with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') check_apply_test(tags_to_apply, get_configuration['tags']) scheduled = get_configuration['metadata']['fim_mode'] == 'scheduled' whodata = get_configuration['metadata']['fim_mode'] == 'whodata' file1 = 'regular1' # If symlink is pointing to a directory, we need to add files and expect their 'added' event (only if the file # is being created withing the pointed directory if main_folder == testdir_target: fim.create_file(fim.REGULAR, main_folder, file1, content='') fim.create_file(fim.REGULAR, aux_folder, file1, content='') fim.check_time_travel(scheduled, monitor=wazuh_log_monitor) add = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event, error_message='Did not receive expected "Sending FIM event: ..." event' ).result() assert 'added' in add['data']['type'] and file1 in add['data']['path'], \ f"'added' event not matching for {file1}" with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') else: fim.create_file(fim.REGULAR, aux_folder, file1, content='') with pytest.raises(TimeoutError): event = wazuh_log_monitor.start(timeout=3, callback=fim.callback_detect_event) logger.error(f'Unexpected event {event.result()}') raise AttributeError(f'Unexpected event {event.result()}') # Change the target of the symlink and expect events while there's no syscheck scan # Don't expect events from the new target if tags_to_apply == {'monitored_dir'}: modify_symlink(aux_folder, os.path.join(testdir_link, 'symlink2')) else: modify_symlink(aux_folder, os.path.join(testdir_link, 'symlink'), file=file1) modify_and_check_events(main_folder, aux_folder, 'Sample number one') wait_for_symlink_check(wazuh_log_monitor) fim.wait_for_audit(whodata, wazuh_log_monitor) # Expect events the other way around now modify_and_check_events(aux_folder, main_folder, 'Sample number two')
action='store') return parser.parse_args() if __name__ == '__main__': log_level = {'DEBUG': 10, 'ERROR': 40} options = get_script_arguments() events = int(options.n_events) modified = int(options.modified_events) logger.setLevel(log_level[options.debug_level]) original_conf = set_syscheck_config() create_syscheck_environment() try: mitm = generate_analysisd_yaml(n_events=events, modify_events=modified) mitm.shutdown() except FileNotFoundError: logger.error( 'Could not generate the YAML. Please clean the environment.') delete_sockets() except TimeoutError: logger.error( 'Timeout generating necessary events. Please clean the environment.' ) finally: set_syscheck_backup(original_conf) clean_syscheck_environment() kill_daemons() control_service('start')