From e426338da33f6c4cdcb4400185bb68d9a7187ad0 Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 14 Jun 2023 16:23:14 +0200 Subject: [PATCH 01/11] feat(#28): add configuration methods --- .../system/wazuh_handler.py | 314 +++++++++++++++--- 1 file changed, 268 insertions(+), 46 deletions(-) diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index 59f7e02..12f782c 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -3,8 +3,10 @@ # This program is free software; you can redistribute it and/or modify it under the terms of GPLv2 import os -import re +import yaml +import xml.etree.ElementTree as ET from multiprocessing.pool import ThreadPool +from typing import List from wazuh_qa_framework.generic_modules.logging.base_logger import BaseLogger from wazuh_qa_framework.global_variables.daemons import WAZUH_ANGENT_WINDOWS_SERVICE_NAME @@ -18,6 +20,31 @@ } +def configure_local_internal_options(new_conf): + local_internal_configuration_string = '' + for option_name, option_value in new_conf.items(): + local_internal_configuration_string += f"{str(option_name)}={str(option_value)}\n" + return local_internal_configuration_string + + +def configure_ossec_conf(new_conf, template): + new_configuration = ''.join(set_section_wazuh_conf(new_conf, template)) + return new_configuration + + +def configure_api_yaml(new_conf): + new_configuration = yaml.dump(new_conf) + return new_configuration + + +conf_functions = { + 'local_internal_options.conf': configure_local_internal_options, + 'ossec.conf': configure_ossec_conf, + 'agent.conf': configure_ossec_conf, + 'api.yaml': configure_api_yaml +} + + def get_configuration_directory_path(custom_installation_path=None, os_host='linux'): installation_path = custom_installation_path if custom_installation_path else DEFAULT_INSTALL_PATH[os_host] return installation_path if os_host == 'windows' else os.path.join(installation_path, 'etc') @@ -40,7 +67,7 @@ def get_api_directory(custom_installation_path=None): def get_api_configuration_directory(custom_installation_path=None): installation_path = custom_installation_path if custom_installation_path else DEFAULT_INSTALL_PATH['linux'] - return os.path.join(get_api_directory(custom_installation_path), 'configuration') + return os.path.join(get_api_directory(installation_path), 'configuration') def get_alert_directory_path(custom_installation_path=None): @@ -68,10 +95,10 @@ def get_group_configuration_directory(custom_installation_path=None, os_host='li installation_path = custom_installation_path if custom_installation_path else DEFAULT_INSTALL_PATH[os_host] group_configuration_path = None if component == 'manager': - group_configuration_path = os.path.join(get_shared_directory_path(custom_installation_path, os_host), + group_configuration_path = os.path.join(get_shared_directory_path(installation_path, os_host), group) else: - group_configuration_path = os.path.join(get_shared_directory_path(custom_installation_path, os_host)) + group_configuration_path = os.path.join(get_shared_directory_path(installation_path, os_host)) return group_configuration_path @@ -132,7 +159,7 @@ def get_wazuh_file_path(custom_installation_path=None, os_host='linux', file_nam 'files': ['agent.conf'], 'path_calculator': lambda filename: os.path.join(get_group_configuration_directory(installation_path, os_host, - group_name=group, + group=group, component=component), filename) } @@ -340,7 +367,7 @@ def get_ruleset_directory_path(self, host): return ruleset_directory_path - def configure_host(self, host, configuration_host): + def configure_host(self, host, configuration_file, configuration_values): """Configure ossec.conf, agent.conf, api.conf and local_internal_options of specified host of the environment Configuration should fit the format expected for each configuration file: - ossec and agent.conf configuration should be provided as a list of configuration sections section. @@ -369,9 +396,36 @@ def configure_host(self, host, configuration_host): 'value': 121.1.3.1 Args: host (str): Hostname - configuration_host (Map): Map with new hosts configuration + configuration_file (str): File name to be configured + configuration_values (dict): Dictionary with the new configuration """ - pass + self.logger.debug(f"Configuring {configuration_file} in {host}") + + if configuration_file not in conf_functions: + raise Exception(f"Invalid operation for {configuration_file} configuration file. Please select one \ + of the following: {conf_functions.keys()}") + + # Get group folder and new configuration for agent.conf + group = configuration_values.get('group', 'default') if configuration_file == 'agent.conf' else None + configuration_values = (configuration_values['configuration'] if configuration_file == 'agent.conf' + else configuration_values) + + # Get configuration file path + host_configuration_file_path = self.get_file_fullpath(host, configuration_file, group) + + parameters = {'new_conf': configuration_values} + + # Get template for ossec.conf and agent.conf + if configuration_file == 'ossec.conf' or configuration_file == 'agent.conf': + current_configuration = self.get_file_content(host, host_configuration_file_path, become=True) + parameters.update({'template': current_configuration}) + + # Set new configuration + new_configuration = conf_functions[configuration_file](**parameters) + self.modify_file_content(host, host_configuration_file_path, new_configuration, + not self.is_windows(host), self.is_windows(host)) + + self.logger.debug(f"{configuration_file} in {host} configured successfully") def configure_environment(self, configuration_hosts, parallel=True): """Configure multiple hosts at the same time. @@ -396,7 +450,17 @@ def configure_environment(self, configuration_hosts, parallel=True): configuration_host (Map): Map with new hosts configuration parallel(Boolean): Enable parallel tasks """ - pass + self.logger.info('Configuring environment') + if parallel: + host_configuration_map = [] + for host, configuration in configuration_hosts.items(): + for configuration_file, configuration_values in configuration.items(): + host_configuration_map.append((host, configuration_file, configuration_values)) + self.pool.starmap(self.configure_host, host_configuration_map) + else: + for host, configurations in configuration_hosts.items(): + self.configure_host(host, configurations) + self.logger.info('Environment configured successfully') def change_agents_configure_manager(self, agent_list, manager, use_manager_name=True): """Change configured manager of specified agent @@ -517,13 +581,13 @@ def restart_agent(self, host): Args: host (str): Hostname """ - self.logger.debug(f'Restarting agent {host}') + self.logger.debug(f"Restarting agent {host}") service_name = WAZUH_ANGENT_WINDOWS_SERVICE_NAME if self.is_windows(host) else 'wazuh-agent' if self.is_agent(host): self.control_service(host, service_name, 'restarted') - self.logger.debug(f'Agent {host} restarted successfully') + self.logger.debug(f"Agent {host} restarted successfully") else: - raise ValueError(f'Host {host} is not an agent') + raise ValueError(f"Host {host} is not an agent") def restart_agents(self, agent_list=None, parallel=True): """Restart list of agents @@ -532,13 +596,13 @@ def restart_agents(self, agent_list=None, parallel=True): agent_list (list, optional): Agent list. Defaults to None. parallel (bool, optional): Parallel execution. Defaults to True. """ - self.logger.info(f'Restarting agents: {agent_list}') + self.logger.info(f"Restarting agents: {agent_list}") if parallel: - agent_restart_tasks = self.pool.map(self.restart_agent, agent_list) + self.pool.map(self.restart_agent, agent_list) else: for agent in agent_list: self.restart_agent(agent) - self.logger.info(f'Agents restarted successfully: {agent_list}') + self.logger.info(f"Agents restarted successfully: {agent_list}") def restart_manager(self, host): """Restart manager @@ -546,12 +610,12 @@ def restart_manager(self, host): Args: host (str): Hostname """ - self.logger.debug(f'Restarting manager {host}') + self.logger.debug(f"Restarting manager {host}") if self.is_manager(host): self.control_service(host, 'wazuh-manager', 'restarted', become=True) - self.logger.debug(f'Manager {host} restarted successfully') + self.logger.debug(f"Manager {host} restarted successfully") else: - ValueError(f'Host {host} is not a manager') + ValueError(f"Host {host} is not a manager") def restart_managers(self, manager_list, parallel=True): """Restart managers @@ -560,13 +624,13 @@ def restart_managers(self, manager_list, parallel=True): manager_list (list): Managers list parallel (bool, optional): Parallel execution. Defaults to True. """ - self.logger.info(f'Restarting managers: {manager_list}') + self.logger.info(f"Restarting managers: {manager_list}") if parallel: self.pool.map(self.restart_manager, manager_list) else: for manager in manager_list: self.restart_manager(manager) - self.logger.info(f'Managers restarted successfully: {manager_list}') + self.logger.info(f"Managers restarted successfully: {manager_list}") def stop_agent(self, host): """Stop agent @@ -574,13 +638,13 @@ def stop_agent(self, host): Args: host (str): Hostname """ - self.logger.debug(f'Stopping agent {host}') - service_name = WAZUH_ANGENT_WINDOWS_SERVICE_NAME if is_windows(host) else 'wazuh-agent' + self.logger.debug(f"Stopping agent {host}") + service_name = WAZUH_ANGENT_WINDOWS_SERVICE_NAME if self.is_windows(host) else 'wazuh-agent' if self.is_agent(host): self.control_service(host, service_name, 'stopped') - self.logger.debug(f'Agent {host} stopped successfully') + self.logger.debug(f"Agent {host} stopped successfully") else: - raise ValueError(f'Host {host} is not an agent') + raise ValueError(f"Host {host} is not an agent") def stop_agents(self, agent_list=None, parallel=True): """Stop agents @@ -589,13 +653,13 @@ def stop_agents(self, agent_list=None, parallel=True): agent_list(list, optional): Agents list. Defaults to None parallel (bool, optional): Parallel execution. Defaults to True. """ - self.logger.info(f'Stopping agents: {agent_list}') + self.logger.info(f"Stopping agents: {agent_list}") if parallel: self.pool.map(self.stop_agent, agent_list) else: for agent in agent_list: self.restart_agent(agent) - self.logger.info(f'Agents stopped successfully: {agent_list}') + self.logger.info(f"Agents stopped successfully: {agent_list}") def stop_manager(self, host): """Stop manager @@ -603,12 +667,12 @@ def stop_manager(self, host): Args: host (str): Hostname """ - self.logger.debug(f'Stopping manager {host}') + self.logger.debug(f"Stopping manager {host}") if self.is_manager(host): self.control_service(host, 'wazuh-manager', 'stopped', become=True) - self.logger.debug(f'Manager {host} stopped successfully') + self.logger.debug(f"Manager {host} stopped successfully") else: - raise ValueError(f'Host {host} is not a manager') + raise ValueError(f"Host {host} is not a manager") def stop_managers(self, manager_list, parallel=True): """Stop managers @@ -617,13 +681,13 @@ def stop_managers(self, manager_list, parallel=True): manager_list (list): Managers list parallel (bool, optional): Parallel execution. Defaults to True. """ - self.logger.info(f'Stopping managers: {manager_list}') + self.logger.info(f"Stopping managers: {manager_list}") if parallel: self.pool.map(self.stop_manager, manager_list) else: for manager in manager_list: self.restart_manager(manager) - self.logger.info(f'Stopping managers: {manager_list}') + self.logger.info(f"Stopping managers: {manager_list}") def start_agent(self, host): """Start agent @@ -631,13 +695,13 @@ def start_agent(self, host): Args: host (str): Hostname """ - self.logger.debug(f'Starting agent {host}') - service_name = WAZUH_ANGENT_WINDOWS_SERVICE_NAME if is_windows(host) else 'wazuh-agent' + self.logger.debug(f"Starting agent {host}") + service_name = WAZUH_ANGENT_WINDOWS_SERVICE_NAME if self.is_windows(host) else 'wazuh-agent' if self.is_agent(host): self.control_service(host, service_name, 'started') - self.logger.debug(f'Agent {host} started successfully') + self.logger.debug(f"Agent {host} started successfully") else: - raise ValueError(f'Host {host} is not an agent') + raise ValueError(f"Host {host} is not an agent") def start_agents(self, agent_list, parallel=True): """Start agents @@ -646,13 +710,13 @@ def start_agents(self, agent_list, parallel=True): agent_list (list): Agents list parallel (bool, optional): Parallel execution. Defaults to True. """ - self.logger.info(f'Starting agents: {agent_list}') + self.logger.info(f"Starting agents: {agent_list}") if parallel: self.pool.map(self.start_agent, agent_list) else: for agent in agent_list: self.start_agent(agent) - self.logger.info(f'Agents started successfully: {agent_list}') + self.logger.info(f"Agents started successfully: {agent_list}") def start_manager(self, host): """Start manager @@ -660,12 +724,12 @@ def start_manager(self, host): Args: host (str): Hostname """ - self.logger.debug(f'Starting manager {host}') + self.logger.debug(f"Starting manager {host}") if self.is_manager(host): self.control_service(host, 'wazuh-manager', 'started', become=True) - self.logger.debug(f'Manager {host} started successfully') + self.logger.debug(f"Manager {host} started successfully") else: - raise ValueError(f'Host {host} is not a manager') + raise ValueError(f"Host {host} is not a manager") def start_managers(self, manager_list, parallel=True): """Start managers @@ -674,13 +738,13 @@ def start_managers(self, manager_list, parallel=True): manager_list (list): Managers list parallel (bool, optional): Parallel execution. Defaults to True. """ - self.logger.info(f'Starting managers: {manager_list}') + self.logger.info(f"Starting managers: {manager_list}") if parallel: self.pool.map(self.start_manager, manager_list) else: for manager in manager_list: self.start_manager(manager) - self.logger.info(f'Managers started successfully: {manager_list}') + self.logger.info(f"Managers started successfully: {manager_list}") def restart_environment(self, parallel=True): """Restart all agents and manager in the environment @@ -727,11 +791,11 @@ def stop_environment(self, parallel=True): self.pool.map(self.stop_agent, agent_list) else: self.logger.info(message='Stopping environment: Managers') - for manager in get_managers(): + for manager in self.get_managers(): self.stop_manager(manager) self.logger.info(message='Stopping environment: Agents') - for agent in get_agents(): + for agent in self.get_agents(): self.stop_agent(agent) self.logger.info('Stopping environment') @@ -754,11 +818,11 @@ def start_environment(self, parallel=True): self.pool.map(self.start_agent, agent_list) else: self.logger.info(message='Starting environment: Managers') - for manager in get_managers(): + for manager in self.get_managers(): self.start_manager(manager) self.logger.info(message='Starting environment: Agents') - for agent in get_agents(): + for agent in self.get_agents(): self.start_agent(agent) self.logger.info('Environment started successfully') @@ -843,3 +907,161 @@ def is_manager(self, host): bool: True if host is manager """ return host in self.get_managers() + +def set_section_wazuh_conf(sections, template=None): + """ + Set a configuration in a section of Wazuh. It replaces the content if it exists. + + Args: + sections (list): List of dicts with section and new elements + section (str, optional): Section of Wazuh configuration to replace. Default `'syscheck'` + new_elements (list, optional) : List with dictionaries for settings elements in the section. Default `None` + template (list of string, optional): File content template + + Returns: + List of str: List of str with the custom Wazuh configuration. + """ + + def create_elements(section: ET.Element, elements: List): + """ + Insert new elements in a Wazuh configuration section. + + Args: + section (ET.Element): Section where the element will be inserted. + elements (list): List with the new elements to be inserted. + Returns: + ET.ElementTree: Modified Wazuh configuration. + """ + tag = None + for element in elements: + for tag_name, properties in element.items(): + tag = ET.SubElement(section, tag_name) + new_elements = properties.get('elements') + attributes = properties.get('attributes') + if attributes is not None: + for attribute in attributes: + if isinstance(attribute, dict): # noqa: E501 + for attr_name, attr_value in attribute.items(): + tag.attrib[attr_name] = str(attr_value) + if new_elements: + create_elements(tag, new_elements) + else: + tag.text = str(properties.get('value')) + attributes = properties.get('attributes') + if attributes: + for attribute in attributes: + if attribute is not None and isinstance(attribute, dict): # noqa: E501 + for attr_name, attr_value in attribute.items(): + tag.attrib[attr_name] = str(attr_value) + tag.tail = "\n " + tag.tail = "\n " + + def purge_multiple_root_elements(str_list: List[str], root_delimeter: str = "") -> List[str]: + """ + Remove from the list all the lines located after the root element ends. + + This operation is needed before attempting to convert the list to ElementTree because if the ossec.conf had more + than one `` element as root the conversion would fail. + + Args: + str_list (list or str): The content of the ossec.conf file in a list of str. + root_delimeter (str, optional: The expected string to identify when the first root element ends, + by default "" + + Returns: + list of str : The first N lines of the specified str_list until the root_delimeter is found. The rest of + the list will be ignored. + """ + line_counter = 0 + for line in str_list: + line_counter += 1 + if root_delimeter in line: + return str_list[0:line_counter] + else: + return str_list + + def to_elementTree(str_list: List[str], root_delimeter: str = "") -> ET.ElementTree: + """ + Turn a list of str into an ElementTree object. + + As ElementTree does not support xml with more than one root element this function will parse the list first with + `purge_multiple_root_elements` to ensure there is only one root element. + + Args: + str_list (list of str): A list of strings with every line of the ossec conf. + + Returns: + ElementTree: A ElementTree object with the data of the `str_list` + """ + str_list = purge_multiple_root_elements(str_list, root_delimeter) + return ET.ElementTree(ET.fromstringlist(str_list)) + + def to_str_list(elementTree: ET.ElementTree) -> List[str]: + """ + Turn an ElementTree object into a list of str. + + Args: + elementTree (ElementTree): A ElementTree object with all the data of the ossec.conf. + + Returns: + (list of str): A list of str containing all the lines of the ossec.conf. + """ + return ET.tostringlist(elementTree.getroot(), encoding="unicode") + + def find_module_config(wazuh_conf: ET.ElementTree, section: str, attributes: List[dict]) -> ET.ElementTree: + r""" + Check if a certain configuration section exists in ossec.conf and returns the corresponding block if exists. + (This extra function has been necessary to implement it to configure the wodle blocks, since they have the same + section but different attributes). + + Args: + wazuh_conf (ElementTree): An ElementTree object with all the data of the ossec.conf + section (str): Name of the tag or configuration section to search for. For example: vulnerability_detector + attributes (list of dict): List with section attributes. Needed to check if the section exists with all the + searched attributes and values. For example (wodle section) [{'name': 'syscollector'}] + Returns: + ElementTree: An ElementTree object with the section data found in ossec.conf. None if nothing was found. + """ + if attributes is None: + return wazuh_conf.find(section) + else: + attributes_query = ''.join([f"[@{attribute}='{value}']" for index, _ in enumerate(attributes) + for attribute, value in attributes[index].items()]) + query = f"{section}{attributes_query}" + + try: + return wazuh_conf.find(query) + except AttributeError: + return None + + # Generate a ElementTree representation of the previous list to work with its sections + root_delimeter = '' if '' in template else '' + wazuh_conf = to_elementTree(purge_multiple_root_elements(template, root_delimeter), root_delimeter) + for section in sections: + attributes = section.get('attributes') + section_conf = find_module_config(wazuh_conf, section['section'], attributes) + # Create section if it does not exist, clean otherwise + if not section_conf: + section_conf = ET.SubElement(wazuh_conf.getroot(), section['section']) + section_conf.text = '\n ' + section_conf.tail = '\n\n ' + else: + prev_text = section_conf.text + prev_tail = section_conf.tail + section_conf.clear() + section_conf.text = prev_text + section_conf.tail = prev_tail + + # Insert section attributes + if attributes: + for attribute in attributes: + if attribute is not None and isinstance(attribute, dict): # noqa: E501 + for attr_name, attr_value in attribute.items(): + section_conf.attrib[attr_name] = str(attr_value) + + # Insert elements + new_elements = section.get('elements', list()) + if new_elements: + create_elements(section_conf, new_elements) + + return to_str_list(wazuh_conf) From 542393da0bba8dcac33046d3205019ec2c74ee4a Mon Sep 17 00:00:00 2001 From: Julia Date: Wed, 14 Jun 2023 17:13:27 +0200 Subject: [PATCH 02/11] feat(#28): add function to change configured manager --- .../system/wazuh_handler.py | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index 12f782c..dd61cc0 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -462,15 +462,29 @@ def configure_environment(self, configuration_hosts, parallel=True): self.configure_host(host, configurations) self.logger.info('Environment configured successfully') - def change_agents_configure_manager(self, agent_list, manager, use_manager_name=True): + def change_agents_configured_manager(self, agent_list, manager, use_manager_name=True): """Change configured manager of specified agent Args: - agent (str): Agent name. + agent_list (list): List of agents that configuration will be changed. manager (str): Manager name in the environment/Manager or IP. use_manager_name (Boolean): Replace manager name with manager IP. Default True """ - pass + if type(agent_list) != list: + raise TypeError('Expected a list of agents') + + new_configuration = {} + new_manager = manager if use_manager_name else self.get_host_ansible_ip(manager) + + server_block = {'server': {'elements': [{'address': {'value': new_manager}}]}} + configuration = [{'section': 'client', 'elements': [server_block]}] + + for agent in agent_list: + new_configuration[agent] = { + 'ossec.conf': configuration + } + + self.configure_environment(new_configuration) def backup_host_configuration(self, configuration_list): """Backup specified files in @@ -908,6 +922,7 @@ def is_manager(self, host): """ return host in self.get_managers() + def set_section_wazuh_conf(sections, template=None): """ Set a configuration in a section of Wazuh. It replaces the content if it exists. From ddaf9e5c6f91e8a8c81d144a98a3670d9c92772a Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 16 Jun 2023 12:10:21 +0200 Subject: [PATCH 03/11] feat(#28): add backup and restore functions --- .../system/wazuh_handler.py | 77 ++++++++++++++++--- 1 file changed, 68 insertions(+), 9 deletions(-) diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index dd61cc0..99f6b09 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -19,6 +19,10 @@ 'darwin': '/Library/Ossec' } +DEFAULT_TEMPORAL_DIRECTORY = { + 'linux': '/tmp', + 'windows': 'C:\\Users\\qa\\AppData\Local\Temp' +} def configure_local_internal_options(new_conf): local_internal_configuration_string = '' @@ -459,7 +463,8 @@ def configure_environment(self, configuration_hosts, parallel=True): self.pool.starmap(self.configure_host, host_configuration_map) else: for host, configurations in configuration_hosts.items(): - self.configure_host(host, configurations) + for configuration_file, configuration_values in configurations.items(): + self.configure_host(host, configuration_file, configuration_values) self.logger.info('Environment configured successfully') def change_agents_configured_manager(self, agent_list, manager, use_manager_name=True): @@ -470,6 +475,7 @@ def change_agents_configured_manager(self, agent_list, manager, use_manager_name manager (str): Manager name in the environment/Manager or IP. use_manager_name (Boolean): Replace manager name with manager IP. Default True """ + self.logger.debug('Changing configured manager') if type(agent_list) != list: raise TypeError('Expected a list of agents') @@ -485,17 +491,30 @@ def change_agents_configured_manager(self, agent_list, manager, use_manager_name } self.configure_environment(new_configuration) + self.logger.debug('Changed configured manager successfully') - def backup_host_configuration(self, configuration_list): - """Backup specified files in + def backup_host_configuration(self, host, file, group=None): + """Backup specified files in host Args: configuration_list (dict): Host configuration files to backup Returns: dict: Host backup filepaths """ + self.logger.debug(f"Creating {file} backup on {host}") + backup_paths = {host: {}} + host_configuration_file_path = self.get_file_fullpath(host, file, group) + temporal_folder = DEFAULT_TEMPORAL_DIRECTORY[self.get_ansible_host_os(host)] + backup_file = os.path.join(temporal_folder, file + '.backup',) + backup_paths[host][host_configuration_file_path] = backup_file + + self.copy_file(host, host_configuration_file_path, backup_file, remote_src=True, + become=not self.is_windows(host)) + + self.logger.debug(f"Created {file} backup on {host} successfully") + return backup_paths - def backup_environment_configuration(self, configuration_list, parallel=True): + def backup_environment_configuration(self, configuration_hosts, parallel=True): """Backup specified files in all hosts Args: @@ -503,23 +522,63 @@ def backup_environment_configuration(self, configuration_list, parallel=True): Returns: dict: Host backup filepaths """ - pass + self.logger.info('Creating backup') + backup_configuration = [] + if parallel: + host_configuration_map = [] + for host, configuration in configuration_hosts.items(): + for file in configuration['files']: + group = configuration['group'] if file == 'agent.conf' else None + host_configuration_map.append((host, file, group)) + backup_configuration = self.pool.starmap(self.backup_host_configuration, host_configuration_map) - def restore_host_backup_configuration(self, backup_configuration): + else: + for host, configuration in configuration_hosts.items(): + for file in configuration['files']: + group = configuration['group'] if file == 'agent.conf' else None + backup_map = (self.backup_host_configuration(host, file, group)) + backup_configuration.append(backup_map) + + final_backup_configuration = {} + for backup_conf_host in backup_configuration: + for host, file in backup_conf_host.items(): + if host in final_backup_configuration: + final_backup_configuration[host].update(file) + else: + final_backup_configuration[host] = file + + self.logger.info('Created backup successfully') + return final_backup_configuration + + def restore_host_backup_configuration(self, host, dest_file, backup_file): """Restore backup configuration Args: backup_configuration (dict): Backup configuration filepaths """ - pass + self.logger.debug(f"Restoring {dest_file} backup on {host}") + self.copy_file(host=host, dest_path=dest_file, + src_path=backup_file, remote_src=True, become=not self.is_windows(host)) + self.logger.debug(f"Restored {dest_file} backup on {host} succesfully") - def restore_environment_backup_configuration(self, backup_configuration, parallel=True): + def restore_environment_backup_configuration(self, backup_configurations, parallel=False): """Restore environment backup configuration Args: backup_configuration (dict): Backup configuration filepaths """ - pass + self.logger.info('Restoring backup') + if parallel: + host_configuration_map = [] + for host, files in backup_configurations.items(): + for dest_file, backup_file in files.items(): + host_configuration_map.append((host, dest_file, backup_file)) + self.pool.starmap(self.restore_host_backup_configuration, host_configuration_map) + else: + for host, files in backup_configurations.items(): + for dest_file, backup_file in files.items(): + self.restore_host_backup_configuration(host, dest_file, backup_file) + self.logger.info('Restored backup successfully') def log_search(self, host, pattern, timeout, file, escape=False, output_file='log_search_output.json'): """Search log in specified host file From a26cf0ad35df07829f1746851a7817364ecdaf45 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 16 Jun 2023 13:40:39 +0200 Subject: [PATCH 04/11] feat(#28): move set_section_wazuh_conf to configuration module --- .../generic_modules/tools/configuration.py | 222 ++++++++++++++++++ .../system/wazuh_handler.py | 163 +------------ 2 files changed, 224 insertions(+), 161 deletions(-) create mode 100644 src/wazuh_qa_framework/generic_modules/tools/configuration.py diff --git a/src/wazuh_qa_framework/generic_modules/tools/configuration.py b/src/wazuh_qa_framework/generic_modules/tools/configuration.py new file mode 100644 index 0000000..6bfae5a --- /dev/null +++ b/src/wazuh_qa_framework/generic_modules/tools/configuration.py @@ -0,0 +1,222 @@ +import xml.etree.ElementTree as ET +from typing import List + + +unlimited_sections = ['localfile', 'command', 'active-response'] + + +# customize _serialize_xml to avoid lexicographical order in XML attributes +def _serialize_xml(write, elem, qnames, namespaces, + short_empty_elements, **kwargs): + tag = elem.tag + text = elem.text + if tag is ET.Comment: + write("" % text) + elif tag is ET.ProcessingInstruction: + write("" % text) + else: + tag = qnames[tag] + if tag is None: + if text: + write(ET._escape_cdata(text)) + for e in elem: + _serialize_xml(write, e, qnames, None, + short_empty_elements=short_empty_elements) + else: + write("<" + tag) + items = list(elem.items()) + if items or namespaces: + if namespaces: + for v, k in sorted(namespaces.items(), + key=lambda x: x[1]): # sort on prefix + if k: + k = ":" + k + write(" xmlns%s=\"%s\"" % ( + k, + ET._escape_attrib(v) + )) + for k, v in items: # avoid lexicographical order for XML attributes + if isinstance(k, ET.QName): + k = k.text + if isinstance(v, ET.QName): + v = qnames[v.text] + else: + v = ET._escape_attrib(v) + write(" %s=\"%s\"" % (qnames[k], v)) + if text or len(elem) or not short_empty_elements: + write(">") + if text: + write(ET._escape_cdata(text)) + for e in elem: + _serialize_xml(write, e, qnames, None, + short_empty_elements=short_empty_elements) + write("") + else: + write(" />") + if elem.tail: + write(ET._escape_cdata(elem.tail)) + + +def set_section_wazuh_conf(sections, template=None): + """ + Set a configuration in a section of Wazuh. It replaces the content if it exists. + + Args: + sections (list): List of dicts with section and new elements + section (str, optional): Section of Wazuh configuration to replace. Default `'syscheck'` + new_elements (list, optional) : List with dictionaries for settings elements in the section. Default `None` + template (list of string, optional): File content template + + Returns: + List of str: List of str with the custom Wazuh configuration. + """ + + def create_elements(section: ET.Element, elements: List): + """ + Insert new elements in a Wazuh configuration section. + + Args: + section (ET.Element): Section where the element will be inserted. + elements (list): List with the new elements to be inserted. + Returns: + ET.ElementTree: Modified Wazuh configuration. + """ + tag = None + for element in elements: + for tag_name, properties in element.items(): + tag = ET.SubElement(section, tag_name) + new_elements = properties.get('elements') + attributes = properties.get('attributes') + if attributes is not None: + for attribute in attributes: + if isinstance(attribute, dict): # noqa: E501 + for attr_name, attr_value in attribute.items(): + tag.attrib[attr_name] = str(attr_value) + if new_elements: + create_elements(tag, new_elements) + else: + tag.text = str(properties.get('value')) + attributes = properties.get('attributes') + if attributes: + for attribute in attributes: + if attribute is not None and isinstance(attribute, dict): # noqa: E501 + for attr_name, attr_value in attribute.items(): + tag.attrib[attr_name] = str(attr_value) + tag.tail = "\n " + tag.tail = "\n " + + def purge_multiple_root_elements(str_list: List[str], root_delimeter: str = "") -> List[str]: + """ + Remove from the list all the lines located after the root element ends. + + This operation is needed before attempting to convert the list to ElementTree because if the ossec.conf had more + than one `` element as root the conversion would fail. + + Args: + str_list (list or str): The content of the ossec.conf file in a list of str. + root_delimeter (str, optional: The expected string to identify when the first root element ends, + by default "" + + Returns: + list of str : The first N lines of the specified str_list until the root_delimeter is found. The rest of + the list will be ignored. + """ + line_counter = 0 + for line in str_list: + line_counter += 1 + if root_delimeter in line: + return str_list[0:line_counter] + else: + return str_list + + def to_elementTree(str_list: List[str], root_delimeter: str = "") -> ET.ElementTree: + """ + Turn a list of str into an ElementTree object. + + As ElementTree does not support xml with more than one root element this function will parse the list first with + `purge_multiple_root_elements` to ensure there is only one root element. + + Args: + str_list (list of str): A list of strings with every line of the ossec conf. + + Returns: + ElementTree: A ElementTree object with the data of the `str_list` + """ + str_list = purge_multiple_root_elements(str_list, root_delimeter) + return ET.ElementTree(ET.fromstringlist(str_list)) + + def to_str_list(elementTree: ET.ElementTree) -> List[str]: + """ + Turn an ElementTree object into a list of str. + + Args: + elementTree (ElementTree): A ElementTree object with all the data of the ossec.conf. + + Returns: + (list of str): A list of str containing all the lines of the ossec.conf. + """ + return ET.tostringlist(elementTree.getroot(), encoding="unicode") + + def find_module_config(wazuh_conf: ET.ElementTree, section: str, attributes: List[dict]) -> ET.ElementTree: + r""" + Check if a certain configuration section exists in ossec.conf and returns the corresponding block if exists. + (This extra function has been necessary to implement it to configure the wodle blocks, since they have the same + section but different attributes). + + Args: + wazuh_conf (ElementTree): An ElementTree object with all the data of the ossec.conf + section (str): Name of the tag or configuration section to search for. For example: vulnerability_detector + attributes (list of dict): List with section attributes. Needed to check if the section exists with all the + searched attributes and values. For example (wodle section) [{'name': 'syscollector'}] + Returns: + ElementTree: An ElementTree object with the section data found in ossec.conf. None if nothing was found. + """ + if attributes is None: + return wazuh_conf.find(section) + else: + attributes_query = ''.join([f"[@{attribute}='{value}']" for index, _ in enumerate(attributes) + for attribute, value in attributes[index].items()]) + query = f"{section}{attributes_query}" + + try: + return wazuh_conf.find(query) + except AttributeError: + return None + + # Generate a ElementTree representation of the previous list to work with its sections + root_delimeter = '' if '' in template else '' + wazuh_conf = to_elementTree(template, root_delimeter) + for section in sections: + attributes = section.get('attributes') + section_conf = find_module_config(wazuh_conf, section['section'], attributes) + # Create section if it does not exist, clean otherwise + if not section_conf: + section_conf = ET.SubElement(wazuh_conf.getroot(), section['section']) + section_conf.text = '\n ' + section_conf.tail = '\n\n ' + else: + # Add section if there is no limit + if section_conf.tag in unlimited_sections: + section_conf = ET.SubElement(wazuh_conf.getroot(), section['section']) + section_conf.text = '\n ' + section_conf.tail = '\n\n ' + else: + prev_text = section_conf.text + prev_tail = section_conf.tail + section_conf.clear() + section_conf.text = prev_text + section_conf.tail = prev_tail + + # Insert section attributes + if attributes: + for attribute in attributes: + if attribute is not None and isinstance(attribute, dict): # noqa: E501 + for attr_name, attr_value in attribute.items(): + section_conf.attrib[attr_name] = str(attr_value) + + # Insert elements + new_elements = section.get('elements', list()) + if new_elements: + create_elements(section_conf, new_elements) + + return to_str_list(wazuh_conf) diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index 99f6b09..031e465 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -4,11 +4,10 @@ import os import yaml -import xml.etree.ElementTree as ET from multiprocessing.pool import ThreadPool -from typing import List from wazuh_qa_framework.generic_modules.logging.base_logger import BaseLogger +from wazuh_qa_framework.generic_modules.tools.configuration import set_section_wazuh_conf from wazuh_qa_framework.global_variables.daemons import WAZUH_ANGENT_WINDOWS_SERVICE_NAME from wazuh_qa_framework.system.host_manager import HostManager @@ -24,6 +23,7 @@ 'windows': 'C:\\Users\\qa\\AppData\Local\Temp' } + def configure_local_internal_options(new_conf): local_internal_configuration_string = '' for option_name, option_value in new_conf.items(): @@ -980,162 +980,3 @@ def is_manager(self, host): bool: True if host is manager """ return host in self.get_managers() - - -def set_section_wazuh_conf(sections, template=None): - """ - Set a configuration in a section of Wazuh. It replaces the content if it exists. - - Args: - sections (list): List of dicts with section and new elements - section (str, optional): Section of Wazuh configuration to replace. Default `'syscheck'` - new_elements (list, optional) : List with dictionaries for settings elements in the section. Default `None` - template (list of string, optional): File content template - - Returns: - List of str: List of str with the custom Wazuh configuration. - """ - - def create_elements(section: ET.Element, elements: List): - """ - Insert new elements in a Wazuh configuration section. - - Args: - section (ET.Element): Section where the element will be inserted. - elements (list): List with the new elements to be inserted. - Returns: - ET.ElementTree: Modified Wazuh configuration. - """ - tag = None - for element in elements: - for tag_name, properties in element.items(): - tag = ET.SubElement(section, tag_name) - new_elements = properties.get('elements') - attributes = properties.get('attributes') - if attributes is not None: - for attribute in attributes: - if isinstance(attribute, dict): # noqa: E501 - for attr_name, attr_value in attribute.items(): - tag.attrib[attr_name] = str(attr_value) - if new_elements: - create_elements(tag, new_elements) - else: - tag.text = str(properties.get('value')) - attributes = properties.get('attributes') - if attributes: - for attribute in attributes: - if attribute is not None and isinstance(attribute, dict): # noqa: E501 - for attr_name, attr_value in attribute.items(): - tag.attrib[attr_name] = str(attr_value) - tag.tail = "\n " - tag.tail = "\n " - - def purge_multiple_root_elements(str_list: List[str], root_delimeter: str = "") -> List[str]: - """ - Remove from the list all the lines located after the root element ends. - - This operation is needed before attempting to convert the list to ElementTree because if the ossec.conf had more - than one `` element as root the conversion would fail. - - Args: - str_list (list or str): The content of the ossec.conf file in a list of str. - root_delimeter (str, optional: The expected string to identify when the first root element ends, - by default "" - - Returns: - list of str : The first N lines of the specified str_list until the root_delimeter is found. The rest of - the list will be ignored. - """ - line_counter = 0 - for line in str_list: - line_counter += 1 - if root_delimeter in line: - return str_list[0:line_counter] - else: - return str_list - - def to_elementTree(str_list: List[str], root_delimeter: str = "") -> ET.ElementTree: - """ - Turn a list of str into an ElementTree object. - - As ElementTree does not support xml with more than one root element this function will parse the list first with - `purge_multiple_root_elements` to ensure there is only one root element. - - Args: - str_list (list of str): A list of strings with every line of the ossec conf. - - Returns: - ElementTree: A ElementTree object with the data of the `str_list` - """ - str_list = purge_multiple_root_elements(str_list, root_delimeter) - return ET.ElementTree(ET.fromstringlist(str_list)) - - def to_str_list(elementTree: ET.ElementTree) -> List[str]: - """ - Turn an ElementTree object into a list of str. - - Args: - elementTree (ElementTree): A ElementTree object with all the data of the ossec.conf. - - Returns: - (list of str): A list of str containing all the lines of the ossec.conf. - """ - return ET.tostringlist(elementTree.getroot(), encoding="unicode") - - def find_module_config(wazuh_conf: ET.ElementTree, section: str, attributes: List[dict]) -> ET.ElementTree: - r""" - Check if a certain configuration section exists in ossec.conf and returns the corresponding block if exists. - (This extra function has been necessary to implement it to configure the wodle blocks, since they have the same - section but different attributes). - - Args: - wazuh_conf (ElementTree): An ElementTree object with all the data of the ossec.conf - section (str): Name of the tag or configuration section to search for. For example: vulnerability_detector - attributes (list of dict): List with section attributes. Needed to check if the section exists with all the - searched attributes and values. For example (wodle section) [{'name': 'syscollector'}] - Returns: - ElementTree: An ElementTree object with the section data found in ossec.conf. None if nothing was found. - """ - if attributes is None: - return wazuh_conf.find(section) - else: - attributes_query = ''.join([f"[@{attribute}='{value}']" for index, _ in enumerate(attributes) - for attribute, value in attributes[index].items()]) - query = f"{section}{attributes_query}" - - try: - return wazuh_conf.find(query) - except AttributeError: - return None - - # Generate a ElementTree representation of the previous list to work with its sections - root_delimeter = '' if '' in template else '' - wazuh_conf = to_elementTree(purge_multiple_root_elements(template, root_delimeter), root_delimeter) - for section in sections: - attributes = section.get('attributes') - section_conf = find_module_config(wazuh_conf, section['section'], attributes) - # Create section if it does not exist, clean otherwise - if not section_conf: - section_conf = ET.SubElement(wazuh_conf.getroot(), section['section']) - section_conf.text = '\n ' - section_conf.tail = '\n\n ' - else: - prev_text = section_conf.text - prev_tail = section_conf.tail - section_conf.clear() - section_conf.text = prev_text - section_conf.tail = prev_tail - - # Insert section attributes - if attributes: - for attribute in attributes: - if attribute is not None and isinstance(attribute, dict): # noqa: E501 - for attr_name, attr_value in attribute.items(): - section_conf.attrib[attr_name] = str(attr_value) - - # Insert elements - new_elements = section.get('elements', list()) - if new_elements: - create_elements(section_conf, new_elements) - - return to_str_list(wazuh_conf) From 0e84f8f43ac6fa66612609bf14c913e69babb2e5 Mon Sep 17 00:00:00 2001 From: Julia Date: Fri, 16 Jun 2023 16:30:21 +0200 Subject: [PATCH 05/11] fix(#28): fix windows paths and requirements --- pyproject.toml | 3 ++- src/wazuh_qa_framework/system/wazuh_handler.py | 6 +++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b4f78fa..8aeebeb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,7 +27,8 @@ dependencies = [ system_tests_launcher = [ 'ansible>=4.10.0', 'pytest-testinfra>=6.7.0', - 'pytest==7.3.1' + 'pytest==7.3.1', + 'pywinrm>=0.2.2' ] unit_testing = [ 'pytest >= 6.2.5', diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index 031e465..a6e91ae 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -14,13 +14,13 @@ DEFAULT_INSTALL_PATH = { 'linux': '/var/ossec', - 'windows': 'C:\\Program Files\\ossec-agent', + 'windows': 'C:/Program Files (x86)/ossec-agent', 'darwin': '/Library/Ossec' } DEFAULT_TEMPORAL_DIRECTORY = { 'linux': '/tmp', - 'windows': 'C:\\Users\\qa\\AppData\Local\Temp' + 'windows': 'C:/Users/qa/AppData/Local/Temp' } @@ -561,7 +561,7 @@ def restore_host_backup_configuration(self, host, dest_file, backup_file): src_path=backup_file, remote_src=True, become=not self.is_windows(host)) self.logger.debug(f"Restored {dest_file} backup on {host} succesfully") - def restore_environment_backup_configuration(self, backup_configurations, parallel=False): + def restore_environment_backup_configuration(self, backup_configurations, parallel=True): """Restore environment backup configuration Args: From ecc6d4cfc3cca6aa0b171e5cf381839f51bb2df9 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 6 Jul 2023 10:16:48 +0200 Subject: [PATCH 06/11] fix(#28): move configuration functions to module --- .../generic_modules/tools/configuration.py | 26 ++++++++++++++++++ .../system/wazuh_handler.py | 27 +------------------ 2 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/wazuh_qa_framework/generic_modules/tools/configuration.py b/src/wazuh_qa_framework/generic_modules/tools/configuration.py index 6bfae5a..722d8dc 100644 --- a/src/wazuh_qa_framework/generic_modules/tools/configuration.py +++ b/src/wazuh_qa_framework/generic_modules/tools/configuration.py @@ -1,4 +1,5 @@ import xml.etree.ElementTree as ET +import yaml from typing import List @@ -220,3 +221,28 @@ def find_module_config(wazuh_conf: ET.ElementTree, section: str, attributes: Lis create_elements(section_conf, new_elements) return to_str_list(wazuh_conf) + + +def configure_local_internal_options(new_conf): + local_internal_configuration_string = '' + for option_name, option_value in new_conf.items(): + local_internal_configuration_string += f"{str(option_name)}={str(option_value)}\n" + return local_internal_configuration_string + + +def configure_ossec_conf(new_conf, template): + new_configuration = ''.join(set_section_wazuh_conf(new_conf, template)) + return new_configuration + + +def configure_api_yaml(new_conf): + new_configuration = yaml.dump(new_conf) + return new_configuration + + +conf_functions = { + 'local_internal_options.conf': configure_local_internal_options, + 'ossec.conf': configure_ossec_conf, + 'agent.conf': configure_ossec_conf, + 'api.yaml': configure_api_yaml +} diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index a6e91ae..3651ecf 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -7,7 +7,7 @@ from multiprocessing.pool import ThreadPool from wazuh_qa_framework.generic_modules.logging.base_logger import BaseLogger -from wazuh_qa_framework.generic_modules.tools.configuration import set_section_wazuh_conf +from wazuh_qa_framework.generic_modules.tools.configuration import conf_functions from wazuh_qa_framework.global_variables.daemons import WAZUH_ANGENT_WINDOWS_SERVICE_NAME from wazuh_qa_framework.system.host_manager import HostManager @@ -24,31 +24,6 @@ } -def configure_local_internal_options(new_conf): - local_internal_configuration_string = '' - for option_name, option_value in new_conf.items(): - local_internal_configuration_string += f"{str(option_name)}={str(option_value)}\n" - return local_internal_configuration_string - - -def configure_ossec_conf(new_conf, template): - new_configuration = ''.join(set_section_wazuh_conf(new_conf, template)) - return new_configuration - - -def configure_api_yaml(new_conf): - new_configuration = yaml.dump(new_conf) - return new_configuration - - -conf_functions = { - 'local_internal_options.conf': configure_local_internal_options, - 'ossec.conf': configure_ossec_conf, - 'agent.conf': configure_ossec_conf, - 'api.yaml': configure_api_yaml -} - - def get_configuration_directory_path(custom_installation_path=None, os_host='linux'): installation_path = custom_installation_path if custom_installation_path else DEFAULT_INSTALL_PATH[os_host] return installation_path if os_host == 'windows' else os.path.join(installation_path, 'etc') From 8aa3f89bc051fdae4de2a38c2b3ce93387d0f171 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 6 Jul 2023 11:44:59 +0200 Subject: [PATCH 07/11] docs(#28): update examples --- .../system/wazuh_handler.py | 62 +++++++++---------- 1 file changed, 29 insertions(+), 33 deletions(-) diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index 3651ecf..559a077 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -353,26 +353,13 @@ def configure_host(self, host, configuration_file, configuration_values): - local_internal_options configuration should be provided as a map - api.yaml should be provided as a map - Example: - local_internal_options: - remoted.debug: 2 - wazuh_modulesd.debug: 2 - ossec.conf: - - 'section': 'client', - 'elements': - - 'server': - 'elements': - - 'address': - 'value': 121.1.3.1 - agent.conf: - - 'group': 'default', - - configuration: - - 'section': 'client', - 'elements': - - 'server': - 'elements': - - 'address': - 'value': 121.1.3.1 + Examples: + - [('manager1', 'local_internal_options.conf', {'remoted.debug': '2'})] + - [('manager1', 'ossec.conf', [{'section': 'client', 'elements': [{'server': {'elements': [{'address': + {'value': '121.1.3.1'}}]}}]}])] + - [('manager1', 'agent.conf', {'group': 'default', 'configuration': + [{'section': 'client', 'elements': [{'server': {'elements': [{'address': {'value': '121.1.3.1'}}]}}]}]})] + - [('manager1', 'api.yaml', {'logs': {'level': 'debug'}})] Args: host (str): Hostname configuration_file (str): File name to be configured @@ -409,20 +396,29 @@ def configure_host(self, host, configuration_file, configuration_values): def configure_environment(self, configuration_hosts, parallel=True): """Configure multiple hosts at the same time. Example: - wazuh-agent1: - local_internal_options: - remoted.debug: 2 - wazuh_modulesd.debug: 2 + wazuh-manager1: + local_internal_options.conf: + remoted.debug: '2' ossec.conf: - - 'section': 'client', - 'elements': - - 'server': - 'elements': - - 'address': - 'value': 121.1.3.1 - api.yml: - .... - wazuh-agent2: + - section: client + elements: + - server: + elements: + - address: + value: 121.1.3.1 + agent.conf: + group: default + configuration: + - section: client + elements: + - server: + elements: + - address: + value: 121.1.3.1 + api.yaml: + logs: + level: debug + wazuh-agent1: ossec.conf: ... Args: From 1deaf31c7b8565dc46d93ffbdc4d6d1a237a6bdc Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 6 Jul 2023 11:49:38 +0200 Subject: [PATCH 08/11] fix(#28): move xml files to variable --- src/wazuh_qa_framework/generic_modules/tools/configuration.py | 1 + src/wazuh_qa_framework/system/wazuh_handler.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/wazuh_qa_framework/generic_modules/tools/configuration.py b/src/wazuh_qa_framework/generic_modules/tools/configuration.py index 722d8dc..02e4e6a 100644 --- a/src/wazuh_qa_framework/generic_modules/tools/configuration.py +++ b/src/wazuh_qa_framework/generic_modules/tools/configuration.py @@ -4,6 +4,7 @@ unlimited_sections = ['localfile', 'command', 'active-response'] +xml_configuration_files = ['ossec.conf', 'agent.conf'] # customize _serialize_xml to avoid lexicographical order in XML attributes diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index 559a077..30a03c2 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -7,7 +7,7 @@ from multiprocessing.pool import ThreadPool from wazuh_qa_framework.generic_modules.logging.base_logger import BaseLogger -from wazuh_qa_framework.generic_modules.tools.configuration import conf_functions +from wazuh_qa_framework.generic_modules.tools.configuration import conf_functions, xml_configuration_files from wazuh_qa_framework.global_variables.daemons import WAZUH_ANGENT_WINDOWS_SERVICE_NAME from wazuh_qa_framework.system.host_manager import HostManager @@ -382,7 +382,7 @@ def configure_host(self, host, configuration_file, configuration_values): parameters = {'new_conf': configuration_values} # Get template for ossec.conf and agent.conf - if configuration_file == 'ossec.conf' or configuration_file == 'agent.conf': + if configuration_file in xml_configuration_files: current_configuration = self.get_file_content(host, host_configuration_file_path, become=True) parameters.update({'template': current_configuration}) From 24ed2bc7a8939c7815b69fe2f001889f6466ecee Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 6 Jul 2023 11:54:59 +0200 Subject: [PATCH 09/11] fix(#28): add missing comma --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 91e2093..85665b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ system_tests_launcher = [ 'ansible>=4.10.0', 'pytest-testinfra>=6.7.0', 'pytest==7.3.1', - 'pywinrm>=0.2.2' + 'pywinrm>=0.2.2', 'ansible_runner>=2.3.2' ] unit_testing = [ From 49f9322803eb3c49ef2e030d54d9d37fff18b882 Mon Sep 17 00:00:00 2001 From: Julia Date: Thu, 20 Jul 2023 15:12:10 +0200 Subject: [PATCH 10/11] docs: fix args --- src/wazuh_qa_framework/system/wazuh_handler.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/wazuh_qa_framework/system/wazuh_handler.py b/src/wazuh_qa_framework/system/wazuh_handler.py index 1efbb17..4cd620e 100644 --- a/src/wazuh_qa_framework/system/wazuh_handler.py +++ b/src/wazuh_qa_framework/system/wazuh_handler.py @@ -468,7 +468,8 @@ def backup_host_configuration(self, host, file, group=None): """Backup specified files in host Args: - configuration_list (dict): Host configuration files to backup + host (str): Hostname to backup + file (str): File to backup Returns: dict: Host backup filepaths """ @@ -476,7 +477,7 @@ def backup_host_configuration(self, host, file, group=None): backup_paths = {host: {}} host_configuration_file_path = self.get_file_fullpath(host, file, group) temporal_folder = DEFAULT_TEMPORAL_DIRECTORY[self.get_ansible_host_os(host)] - backup_file = os.path.join(temporal_folder, file + '.backup',) + backup_file = os.path.join(temporal_folder, file + '.backup') backup_paths[host][host_configuration_file_path] = backup_file self.copy_file(host, host_configuration_file_path, backup_file, remote_src=True, @@ -489,7 +490,7 @@ def backup_environment_configuration(self, configuration_hosts, parallel=True): """Backup specified files in all hosts Args: - configuration_list (dict): Host configuration files to backup + configuration_hosts(dict): Host configuration files to backup Returns: dict: Host backup filepaths """ @@ -525,7 +526,8 @@ def restore_host_backup_configuration(self, host, dest_file, backup_file): """Restore backup configuration Args: - backup_configuration (dict): Backup configuration filepaths + host (str): Hostname to restore + dest_file (str): File to restore """ self.logger.debug(f"Restoring {dest_file} backup on {host}") self.copy_file(host=host, dest_path=dest_file, @@ -536,7 +538,7 @@ def restore_environment_backup_configuration(self, backup_configurations, parall """Restore environment backup configuration Args: - backup_configuration (dict): Backup configuration filepaths + backup_configurations (dict): Backup configuration filepaths """ self.logger.info('Restoring backup') if parallel: From 72828b5817c622d876884e63dab84e82c9e2175a Mon Sep 17 00:00:00 2001 From: Julia Date: Tue, 1 Aug 2023 10:50:44 +0200 Subject: [PATCH 11/11] fix: change function name --- src/wazuh_qa_framework/generic_modules/tools/configuration.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/wazuh_qa_framework/generic_modules/tools/configuration.py b/src/wazuh_qa_framework/generic_modules/tools/configuration.py index 02e4e6a..e789a10 100644 --- a/src/wazuh_qa_framework/generic_modules/tools/configuration.py +++ b/src/wazuh_qa_framework/generic_modules/tools/configuration.py @@ -131,7 +131,7 @@ def purge_multiple_root_elements(str_list: List[str], root_delimeter: str = " ET.ElementTree: + def to_element_tree(str_list: List[str], root_delimeter: str = "") -> ET.ElementTree: """ Turn a list of str into an ElementTree object. @@ -187,7 +187,7 @@ def find_module_config(wazuh_conf: ET.ElementTree, section: str, attributes: Lis # Generate a ElementTree representation of the previous list to work with its sections root_delimeter = '' if '' in template else '' - wazuh_conf = to_elementTree(template, root_delimeter) + wazuh_conf = to_element_tree(template, root_delimeter) for section in sections: attributes = section.get('attributes') section_conf = find_module_config(wazuh_conf, section['section'], attributes)