diff --git a/deployability/modules/testing/__init__.py b/deployability/modules/testing/__init__.py index 56cb2176a3..563b4233b6 100755 --- a/deployability/modules/testing/__init__.py +++ b/deployability/modules/testing/__init__.py @@ -1,2 +1,2 @@ -from .testing import Tester from .models import InputPayload, ExtraVars +from .testing import Tester diff --git a/deployability/modules/testing/models.py b/deployability/modules/testing/models.py index 481e267bfc..f6bbb540a0 100644 --- a/deployability/modules/testing/models.py +++ b/deployability/modules/testing/models.py @@ -28,15 +28,3 @@ def validate_tests(cls, value) -> list[str]: value = value.split(',') return value - - @field_validator('targets', mode='before') - def validate_targets(cls, values) -> list: - """Validate required fields.""" - - return values - - @model_validator(mode='before') - def validate_dependencies(cls, values) -> list: - """Validate required fields.""" - - return values \ No newline at end of file diff --git a/deployability/modules/testing/playbooks/setup.yml b/deployability/modules/testing/playbooks/setup.yml index 49727c9abc..ffc78ab456 100644 --- a/deployability/modules/testing/playbooks/setup.yml +++ b/deployability/modules/testing/playbooks/setup.yml @@ -1,13 +1,8 @@ -- hosts: all +- hosts: localhost become: true + become_user: "{{ current_user }}" tasks: - - name: Clone tests into the endpoint - block: - - name: Create test directory - file: - path: "{{ working_dir }}" - state: directory - - name: Copy files to endpoints - copy: - src: "{{ local_path }}/" - dest: "{{ working_dir }}" \ No newline at end of file + - name: Cleaning old key ssh-keygen registries + ansible.builtin.command: + cmd: "ssh-keygen -f /home/{{ current_user }}/.ssh/known_hosts -R '{{ item }}'" + loop: {{ hosts_ip }} \ No newline at end of file diff --git a/deployability/modules/testing/playbooks/test.yml b/deployability/modules/testing/playbooks/test.yml index e7eae19510..83ac8493a8 100644 --- a/deployability/modules/testing/playbooks/test.yml +++ b/deployability/modules/testing/playbooks/test.yml @@ -2,12 +2,6 @@ become: true become_user: "{{ current_user }}" tasks: - - - name: Cleaning old key ssh-keygen registries - ansible.builtin.command: - cmd: "ssh-keygen -f /home/{{ current_user }}/.ssh/known_hosts -R '{{ item }}'" - loop: {{ hosts_ip }} - - name: Test {{ test }} for {{ component }} command: "python3 -m pytest modules/testing/tests/test_{{component}}/test_{{ test }}.py -v --wazuh_version={{ wazuh_version }} --wazuh_revision={{ wazuh_revision }} --component={{ component }} --dependencies='{{ dependencies }}' --targets='{{ targets }}' --live={{ live }} -s" args: diff --git a/deployability/modules/testing/testing.py b/deployability/modules/testing/testing.py index 160b9078b1..0c24d88480 100644 --- a/deployability/modules/testing/testing.py +++ b/deployability/modules/testing/testing.py @@ -1,13 +1,13 @@ -import ast - -from pathlib import Path +import json +import os from modules.generic import Ansible, Inventory from modules.generic.utils import Utils +from pathlib import Path from .models import InputPayload, ExtraVars from .utils import logger -import os -import json + + class Tester: _playbooks_dir = Path(__file__).parent / 'playbooks' _setup_playbook = _playbooks_dir / 'setup.yml' @@ -24,51 +24,49 @@ def run(cls, payload: InputPayload) -> None: """ payload = InputPayload(**dict(payload)) extra_vars = cls._get_extra_vars(payload).model_dump() - targets_paths = payload.targets - extra_vars['hosts_ip'] = [] - for target_path in targets_paths: - path = ', '.join(list(eval(target_path).values())) - target = Inventory(**Utils.load_from_yaml((path))) - extra_vars['hosts_ip'].append(target.ansible_host) - logger.info(f"Running tests for {target.ansible_host}") + print(payload) + targets = {} - for item in targets_paths: - dictionary = eval(item) - targets.update(dictionary) - target_string = json.dumps(targets) - extra_vars['targets'] = target_string.replace('"',"") - dependencies_paths = payload.dependencies - for dependency_path in dependencies_paths: - path = ', '.join(list(eval(dependency_path).values())) - dependency = Inventory(**Utils.load_from_yaml((path))) - logger.info(f"Dependencies {dependency.ansible_host}") dependencies = {} - for item in targets_paths: - dictionary = eval(item) - dependencies.update(dictionary) - target_string = json.dumps(dependencies) - extra_vars['dependencies'] = target_string.replace('"',"") + extra_vars['hosts_ip'] = [] + + # Process targets and dependencies + for path_type, paths_list in [("targets", payload.targets), ("dependencies", payload.dependencies)]: + for path in paths_list: + dictionary = eval(path) + inventory = Inventory(**Utils.load_from_yaml(', '.join(dictionary.values()))) + extra_vars['hosts_ip'].extend([inventory.ansible_host] if path_type == "targets" else []) + logger.info(f"Running tests for {(inventory.ansible_host)}") if path_type == "targets" else logger.info(f"Dependencies {inventory.ansible_host}") + if path_type == "targets": + targets.update(dictionary) + else: + dependencies.update(dictionary) + + extra_vars['targets'] = json.dumps(targets).replace('"', "") + extra_vars['dependencies'] = json.dumps(dependencies).replace('"', "") + + # Set extra vars extra_vars['local_host_path'] = str(Path(__file__).parent.parent.parent) extra_vars['current_user'] = os.getlogin() + logger.debug(f"Using extra vars: {extra_vars}") - for target in targets_paths: - target_value = eval(target).values() - target_inventory = Inventory(**Utils.load_from_yaml(str(list(target_value)[0]))) - ansible = Ansible(ansible_data=target_inventory.model_dump()) - cls._setup(ansible, extra_vars['working_dir']) - target_inventory = Inventory(**Utils.load_from_yaml(str(list(eval(targets_paths[0]).values())[0]))) + ansible = None + + # Setup and run tests + target_inventory = Inventory(**Utils.load_from_yaml(str(list(eval(payload.targets[0]).values())[0]))) ansible = Ansible(ansible_data=target_inventory.model_dump()) + cls._setup(ansible, extra_vars) cls._run_tests(payload.tests, ansible, extra_vars) - for target in targets_paths: - target_value = eval(target).values() - target_inventory = Inventory(**Utils.load_from_yaml(str(list(target_value)[0]))) - if payload.cleanup: + # Clean up if required + if payload.cleanup: + for target_path in payload.targets: + target_value = eval(target_path).values() + target_inventory = Inventory(**Utils.load_from_yaml(str(list(target_value)[0]))) logger.info("Cleaning up") cls._cleanup(ansible, extra_vars['working_dir']) - @classmethod def _get_extra_vars(cls, payload: InputPayload) -> ExtraVars: """ @@ -103,17 +101,17 @@ def _run_tests(cls, test_list: list[str], ansible: Ansible, extra_vars: ExtraVar ansible.run_playbook(playbook, extra_vars) @classmethod - def _setup(cls, ansible: Ansible, remote_working_dir: str = '/tmp') -> None: + def _setup(cls, ansible: Ansible, extra_vars: ExtraVars) -> None: """ Setup the environment for the tests. Args: ansible (Ansible): The Ansible object to run the setup. - remote_working_dir (str): The remote working directory. + extra_vars (str): The extra vars for the setup. """ - extra_vars = {'local_path': str(Path(__file__).parent / 'tests'), - 'working_dir': remote_working_dir} - playbook = str(cls._setup_playbook) + rendering_var = {**extra_vars} + template = str(cls._setup_playbook) + playbook = ansible.render_playbook(template, rendering_var) ansible.run_playbook(playbook, extra_vars) @classmethod diff --git a/deployability/modules/testing/tests/__init__.py b/deployability/modules/testing/tests/__init__.py index e69de29bb2..425158bfcc 100755 --- a/deployability/modules/testing/tests/__init__.py +++ b/deployability/modules/testing/tests/__init__.py @@ -0,0 +1,2 @@ +from ..testing import Tester +from ..models import InputPayload, ExtraVars diff --git a/deployability/modules/testing/tests/helpers/agent.py b/deployability/modules/testing/tests/helpers/agent.py index dba94e1ac0..589aee777e 100644 --- a/deployability/modules/testing/tests/helpers/agent.py +++ b/deployability/modules/testing/tests/helpers/agent.py @@ -1,10 +1,13 @@ -import yaml import requests +import socket +import yaml -from .executor import Executor, WazuhAPI -from .generic import HostInformation -from .constants import WAZUH_CONTROL, CLUSTER_CONTROL, AGENT_CONTROL, CLIENT_KEYS, WAZUH_CONF, WAZUH_ROOT from typing import List, Optional +from .constants import WAZUH_CONF, WAZUH_ROOT +from .executor import Executor, WazuhAPI +from .generic import HostInformation, CheckFiles + + class WazuhAgent: @@ -28,7 +31,7 @@ def install_agent(inventory_path, agent_name, wazuh_version, wazuh_revision, liv commands.extend([ f"curl -o wazuh-agent-{wazuh_version}-1.x86_64.rpm https://{s3_url}.wazuh.com/{release}/yum/wazuh-agent-{wazuh_version}-1.x86_64.rpm && sudo WAZUH_MANAGER='MANAGER_IP' WAZUH_AGENT_NAME='{agent_name}' rpm -ihv wazuh-agent-{wazuh_version}-1.x86_64.rpm" ]) - elif distribution == 'rpm' or distribution == 'opensuse-leap' or distribution == 'amzn' and 'aarch64' in architecture: + elif distribution == 'rpm' and 'aarch64' in architecture: commands.extend([ f"curl -o wazuh-agent-{wazuh_version}-1aarch64.rpm https://{s3_url}.wazuh.com/{release}/yum/wazuh-agent-{wazuh_version}-1.aarch64.rpm && sudo WAZUH_MANAGER='MANAGER_IP' WAZUH_AGENT_NAME='{agent_name}' rpm -ihv wazuh-agent-{wazuh_version}-1.aarch64.rpm" ]) @@ -89,15 +92,16 @@ def register_agent(inventory_path, manager_path): with open(manager_path, 'r') as yaml_file: manager_path = yaml.safe_load(yaml_file) host = manager_path.get('ansible_host') - + + internal_ip = HostInformation.get_internal_ip_from_aws_dns(host) if 'amazonaws' in host else host + commands = [ - f"sed -i 's/
MANAGER_IP<\/address>/
{host}<\/address>/g' {WAZUH_CONF}", + f"sed -i 's/
MANAGER_IP<\/address>/
{internal_ip}<\/address>/g' {WAZUH_CONF}", "systemctl restart wazuh-agent" - ] + ] Executor.execute_commands(inventory_path, commands) - @staticmethod def uninstall_agent(inventory_path, wazuh_version=None, wazuh_revision=None) -> None: os_type = HostInformation.get_os_type(inventory_path) @@ -155,6 +159,108 @@ def uninstall_agents( inventories_paths=[], wazuh_version: Optional[List[str]]=N + + + + + + + + + + + + @staticmethod + def install_agent_callback(wazuh_params, agent_name, agent_params): + WazuhAgent.install_agent(agent_params, agent_name, wazuh_params['wazuh_version'], wazuh_params['wazuh_revision'], wazuh_params['live']) + + + @staticmethod + def uninstall_agent_callback(wazuh_params, agent_params): + WazuhAgent.uninstall_agent(agent_params, wazuh_params['wazuh_version'], wazuh_params['wazuh_revision']) + + + @staticmethod + def perform_action_and_scan(agent_params, action_callback): + result = CheckFiles.perform_action_and_scan(agent_params, action_callback) + os_name = HostInformation.get_os_name_from_inventory(agent_params) + if 'debian' in os_name: + filter_data = { + '/boot': {'added': [], 'removed': [], 'modified': ['grubenv']}, + '/usr/bin': { + 'added': [ + 'unattended-upgrade', 'gapplication', 'add-apt-repository', 'gpg-wks-server', 'pkexec', 'gpgsplit', + 'watchgnupg', 'pinentry-curses', 'gpg-zip', 'gsettings', 'gpg-agent', 'gresource', 'gdbus', + 'gpg-connect-agent', 'gpgconf', 'gpgparsemail', 'lspgpot', 'pkaction', 'pkttyagent', 'pkmon', + 'dirmngr', 'kbxutil', 'migrate-pubring-from-classic-gpg', 'gpgcompose', 'pkcheck', 'gpgsm', 'gio', + 'pkcon', 'gpgtar', 'dirmngr-client', 'gpg', 'filebeat', 'gawk', 'curl', 'update-mime-database', + 'dh_installxmlcatalogs', 'appstreamcli', 'lspgpot' + ], + 'removed': [], + 'modified': [] + }, + '/root': {'added': ['trustdb.gpg'], 'removed': [], 'modified': []}, + '/usr/sbin': { + 'added': [ + 'update-catalog', 'applygnupgdefaults', 'addgnupghome', 'install-sgmlcatalog', 'update-xmlcatalog' + ], + 'removed': [], + 'modified': [] + } + } + else: + filter_data = { + '/boot': { + 'added': ['grub2', 'loader', 'vmlinuz', 'System.map', 'config-', 'initramfs'], + 'removed': [], + 'modified': ['grubenv'] + }, + '/usr/bin': {'added': ['filebeat'], 'removed': [], 'modified': []}, + '/root': {'added': ['trustdb.gpg'], 'removed': [], 'modified': []}, + '/usr/sbin': {'added': [], 'removed': [], 'modified': []} + } + + # Use of filters + for directory, changes in result.items(): + if directory in filter_data: + for change, files in changes.items(): + if change in filter_data[directory]: + result[directory][change] = [file for file in files if file.split('/')[-1] not in filter_data[directory][change]] + + return result + + @staticmethod + def perform_install_and_scan_for_agent(agent_params, agent_name, wazuh_params): + action_callback = lambda: WazuhAgent.install_agent_callback(wazuh_params, agent_name, agent_params) + result = WazuhAgent.perform_action_and_scan(agent_params, action_callback) + WazuhAgent.assert_results(result) + + + @staticmethod + def perform_uninstall_and_scan_for_agent(agent_params, wazuh_params): + action_callback = lambda: WazuhAgent.uninstall_agent_callback(wazuh_params, agent_params) + result = WazuhAgent.perform_action_and_scan(agent_params, action_callback) + WazuhAgent.assert_results(result) + + + @staticmethod + def assert_results(result): + categories = ['/root', '/usr/bin', '/usr/sbin', '/boot'] + actions = ['added', 'modified', 'removed'] + # Testing the results + for category in categories: + for action in actions: + assert result[category][action] == [] + + + + + + + + + + ## ----------- api def get_agents_information(wazuh_api: WazuhAPI) -> list: diff --git a/deployability/modules/testing/tests/helpers/executor.py b/deployability/modules/testing/tests/helpers/executor.py index 5f673ee6fb..5844b96b08 100644 --- a/deployability/modules/testing/tests/helpers/executor.py +++ b/deployability/modules/testing/tests/helpers/executor.py @@ -1,9 +1,10 @@ -import yaml -import subprocess +import json import requests +import subprocess import urllib3 +import yaml + from base64 import b64encode -import json class Executor: @@ -24,13 +25,15 @@ def execute_command(inventory_path, command) -> str: "-i", private_key_path, "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", + "-p", str(port), f"{username}@{host}", "sudo", command ] result = subprocess.run(ssh_command, stdout=subprocess.PIPE, text=True) - return result.stdout.replace("\n","") + return result.stdout + @staticmethod def execute_commands(inventory_path, commands=[]) -> dict: @@ -50,41 +53,37 @@ def execute_commands(inventory_path, commands=[]) -> dict: "-i", private_key_path, "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", + "-p", str(port), f"{username}@{host}", "sudo", command ] results[command] = subprocess.run(ssh_command, stdout=subprocess.PIPE, text=True).stdout - return results class WazuhAPI: - def __init__(self, inventory_path, wazuh_user='wazuh', wazuh_password='wazuh'): + def __init__(self, inventory_path): self.inventory_path = inventory_path self.api_url = None self.headers = None - self.wazuh_user = wazuh_user - self.wazuh_password = self._get_wazuh_api_password() urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) self._authenticate() - - def _get_wazuh_api_password(self): - #------------Patch issue https://github.com/wazuh/wazuh-packages/issues/2883--------------------- - file_path = Executor.execute_command(self.inventory_path, 'pwd').replace("\n","") + '/wazuh-install-files/wazuh-passwords.txt ' - if not 'true' in Executor.execute_command(self.inventory_path, f'test -f {file_path} && echo "true" || echo "false"'): - Executor.execute_command(self.inventory_path, 'tar -xvf wazuh-install-files.tar') - return Executor.execute_command(self.inventory_path, "grep api_password wazuh-install-files/wazuh-passwords.txt | head -n 1 | awk '{print $NF}'").replace("'","").replace("\n","") - - def _authenticate(self): with open(self.inventory_path, 'r') as yaml_file: inventory_data = yaml.safe_load(yaml_file) - user = self.wazuh_user - password = self.wazuh_password + user = 'wazuh' + + #----Patch issue https://github.com/wazuh/wazuh-packages/issues/2883------------- + file_path = Executor.execute_command(self.inventory_path, 'pwd').replace("\n","") + '/wazuh-install-files/wazuh-passwords.txt ' + if not 'true' in Executor.execute_command(self.inventory_path, f'test -f {file_path} && echo "true" || echo "false"'): + Executor.execute_command(self.inventory_path, 'tar -xvf wazuh-install-files.tar') + password = Executor.execute_command(self.inventory_path, "grep api_password wazuh-install-files/wazuh-passwords.txt | head -n 1 | awk '{print $NF}'").replace("'","").replace("\n","") + #-------------------------------------------------------------------------------- + login_endpoint = 'security/user/authenticate' host = inventory_data.get('ansible_host') port = '55000' @@ -100,4 +99,3 @@ def _authenticate(self): 'Content-Type': 'application/json', 'Authorization': f'Bearer {token}' } - diff --git a/deployability/modules/testing/tests/helpers/generic.py b/deployability/modules/testing/tests/helpers/generic.py index 815aa21422..1d1e5b1132 100644 --- a/deployability/modules/testing/tests/helpers/generic.py +++ b/deployability/modules/testing/tests/helpers/generic.py @@ -1,14 +1,16 @@ -import yaml +import boto3 import chardet -import time +import os import re import subprocess -from pathlib import Path -import os -from .executor import Executor +import time +import yaml +import socket +from pathlib import Path from .constants import WAZUH_CONTROL, CLIENT_KEYS - +from .executor import Executor +from .utils import Utils class HostInformation: @@ -118,46 +120,76 @@ def get_current_dir(inventory_path) -> str: Returns: str: current directory """ - return Executor.execute_command(inventory_path, 'pwd') + return Executor.execute_command(inventory_path, 'pwd').replace("\n","") + + @staticmethod + def get_internal_ip_from_aws_dns(dns_name): + """ + It returns the private AWS IP from dns_name + + Args: + dns_name (str): host's dns public dns name + + Returns: + str: private ip + """ + ec2 = boto3.client('ec2') + response = ec2.describe_instances(Filters=[{'Name': 'dns-name', 'Values': [dns_name]}]) + if response['Reservations']: + instance = response['Reservations'][0]['Instances'][0] + return instance['PrivateIpAddress'] + else: + return None class HostConfiguration: @staticmethod def sshd_config(inventory_path) -> None: + """ + Configures sshd_config file to connect using password + + Args: + inventory_path: host's inventory path + + """ + commands = ["sudo sed -i '/^PasswordAuthentication/s/^/#/' /etc/ssh/sshd_config", "sudo sed -i '/^PermitRootLogin no/s/^/#/' /etc/ssh/sshd_config", 'echo -e "PasswordAuthentication yes\nPermitRootLogin yes" | sudo tee -a /etc/ssh/sshd_config', 'sudo systemctl restart sshd', 'cat /etc/ssh/sshd_config'] Executor.execute_commands(inventory_path, commands) @staticmethod def disable_firewall(inventory_path) -> None: + """ + Disables firewall + + Args: + inventory_path: host's inventory path + + """ commands = ["sudo systemctl stop firewalld", "sudo systemctl disable firewalld"] Executor.execute_commands(inventory_path, commands) @staticmethod def certs_create(wazuh_version, master_path, dashboard_path, indexer_paths=[], worker_paths=[]) -> None: + """ + Creates wazuh certificates + + Args: + wazuh_version (str): wazuh version + master_path (str): wazuh master inventory_path + dashboard_path (str): wazuh dashboard inventory_path + indexer_paths (list): wazuh indexers list + workers_paths (list): wazuh worker paths list + + """ current_directory = HostInformation.get_current_dir(master_path) wazuh_version = '.'.join(wazuh_version.split('.')[:2]) - with open(master_path, 'r') as yaml_file: - inventory_data = yaml.safe_load(yaml_file) - master = inventory_data.get('ansible_host') - - with open(dashboard_path, 'r') as yaml_file: - inventory_data = yaml.safe_load(yaml_file) - dashboard = inventory_data.get('ansible_host') - - indexers = [] - for indexer_path in indexer_paths: - with open(indexer_path, 'r') as yaml_file: - inventory_data = yaml.safe_load(yaml_file) - indexers.append(inventory_data.get('ansible_host')) - - workers = [] - for worker_path in worker_paths: - with open(worker_path, 'r') as yaml_file: - inventory_data = yaml.safe_load(yaml_file) - workers.append(inventory_data.get('ansible_host')) + master = socket.gethostbyname(Utils.extract_ansible_host(master_path)) + dashboard = socket.gethostbyname(Utils.extract_ansible_host(dashboard_path)) + indexers = [socket.gethostbyname(Utils.extract_ansible_host(indexer_path)) for indexer_path in indexer_paths] + workers = [socket.gethostbyname(Utils.extract_ansible_host(worker_path)) for worker_path in worker_paths] ##Basic commands to setup the config file, add the ip for the master & dashboard os_name = HostInformation.get_os_name_from_inventory(master_path) @@ -165,23 +197,25 @@ def certs_create(wazuh_version, master_path, dashboard_path, indexer_paths=[], w commands = [ f'wget https://packages.wazuh.com/{wazuh_version}/wazuh-install.sh', f'wget https://packages.wazuh.com/{wazuh_version}/config.yml', - f"sed -i '/^\s*#/d' {current_directory}/config.yml", - f"sed -i '0,//s//{master}/' {current_directory}/config.yml", - f"sed -i '0,//s//{dashboard}/' {current_directory}/config.yml" + f"sed -i '/^\s*#/d' {current_directory}/config.yml" ] else: commands = [ f'curl -sO https://packages.wazuh.com/{wazuh_version}/wazuh-install.sh', f'curl -sO https://packages.wazuh.com/{wazuh_version}/config.yml', - f"sed -i '/^\s*#/d' {current_directory}/config.yml", - f"sed -i '0,//s//{master}/' {current_directory}/config.yml", - f"sed -i '0,//s//{dashboard}/' {current_directory}/config.yml" + f"sed -i '/^\s*#/d' {current_directory}/config.yml" ] # Add master tag if there are workers if len(worker_paths) != 0: commands.append(f"""sed -i '/ip: ""/a\ node_type: master' {current_directory}/config.yml""") + # Add manager and dashboard IP + commands.extend([ + f"sed -i '0,//s//{master}/' {current_directory}/config.yml", + f"sed -i '0,//s//{dashboard}/' {current_directory}/config.yml" + ]) + # Adding workers for index, element in reversed(list(enumerate(workers))): commands.append(f'sed -i \'/node_type: master/a\\ - name: wazuh-{index+2}\\n ip: ""\\n node_type: worker\' {current_directory}/config.yml') @@ -210,47 +244,15 @@ def certs_create(wazuh_version, master_path, dashboard_path, indexer_paths=[], w @staticmethod def scp_to(from_inventory_path, to_inventory_path, file_name) -> None: - current_from_directory = HostInformation.get_current_dir(from_inventory_path) - current_to_directory = HostInformation.get_current_dir(to_inventory_path) - with open(from_inventory_path, 'r') as yaml_file: - from_inventory_data = yaml.safe_load(yaml_file) - - with open(to_inventory_path, 'r') as yaml_file: - to_inventory_data = yaml.safe_load(yaml_file) - - # Defining variables - from_host = from_inventory_data.get('ansible_host') - from_key = from_inventory_data.get('ansible_ssh_private_key_file') - from_user = from_inventory_data.get('ansible_user') - to_host = to_inventory_data.get('ansible_host') - to_key = to_inventory_data.get('ansible_ssh_private_key_file') - to_user = from_inventory_data.get('ansible_user') - - # Allowing handling permissions - if file_name == 'wazuh-install-files.tar': - Executor.execute_command(from_inventory_path, f'chmod +rw {file_name}') - - # SCP - subprocess.run(f'scp -i {from_key} -o StrictHostKeyChecking=no {from_user}@{from_host}:{current_from_directory}/{file_name} {Path(__file__).parent}' , shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - subprocess.run(f'scp -i {to_key} -o StrictHostKeyChecking=no {Path(__file__).parent}/{file_name} {to_user}@{to_host}:{current_to_directory}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - - # Restoring permissions - if file_name == 'wazuh-install-files.tar': - Executor.execute_command(from_inventory_path, f'chmod 600 {file_name}') - Executor.execute_command(to_inventory_path, f'chmod 600 {file_name}') - - # Deleting file from localhost - file_path = os.path.join(os.path.abspath(os.path.dirname(__file__)), file_name) - - if os.path.exists(file_path): - os.remove(file_path) - print(f"The file {file_name} has been deleted.") - else: - print(f"The file {file_name} does not exist.") + """ + Send via SCP from one host to another host + Args: + from_inventory_path (str): host that owns the file to be sent path + to_inventory_path (str): host that recieves the file path + file_name (str): file name that will be send to home/{user} - @staticmethod - def scp_to(from_inventory_path, to_inventory_path, file_name) -> None: + """ current_from_directory = HostInformation.get_current_dir(from_inventory_path) current_to_directory = HostInformation.get_current_dir(to_inventory_path) with open(from_inventory_path, 'r') as yaml_file: @@ -260,20 +262,23 @@ def scp_to(from_inventory_path, to_inventory_path, file_name) -> None: to_inventory_data = yaml.safe_load(yaml_file) # Defining variables - from_host = from_inventory_data.get('ansible_host') + from_host = socket.gethostbyname(from_inventory_data.get('ansible_host')) from_key = from_inventory_data.get('ansible_ssh_private_key_file') from_user = from_inventory_data.get('ansible_user') - to_host = to_inventory_data.get('ansible_host') + from_port = from_inventory_data.get('ansible_port:') + + to_host = socket.gethostbyname(to_inventory_data.get('ansible_host')) to_key = to_inventory_data.get('ansible_ssh_private_key_file') to_user = from_inventory_data.get('ansible_user') + to_port = to_inventory_data.get('ansible_port:') # Allowing handling permissions if file_name == 'wazuh-install-files.tar': Executor.execute_command(from_inventory_path, f'chmod +rw {file_name}') # SCP - subprocess.run(f'scp -i {from_key} -o StrictHostKeyChecking=no {from_user}@{from_host}:{current_from_directory}/{file_name} {Path(__file__).parent}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) - subprocess.run(f'scp -i {to_key} -o StrictHostKeyChecking=no {Path(__file__).parent}/{file_name} {to_user}@{to_host}:{current_to_directory}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + subprocess.run(f'scp -i {from_key} -o StrictHostKeyChecking=no -P {from_port} {from_user}@{from_host}:{current_from_directory}/{file_name} {Path(__file__).parent}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) + subprocess.run(f'scp -i {to_key} -o StrictHostKeyChecking=no -P {to_port} {Path(__file__).parent}/{file_name} {to_user}@{to_host}:{current_to_directory}', shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True) # Restoring permissions if file_name == 'wazuh-install-files.tar': @@ -379,6 +384,7 @@ def _checkfiles(inventory_path, os_type, directory, filter= None, hash_algorithm @staticmethod def _perform_scan(inventory_path, os_type, directories, filters): + return {directory: CheckFiles._checkfiles(inventory_path, os_type, directory, filters) for directory in directories} @@ -387,11 +393,23 @@ def _calculate_changes(initial_scan, second_scan): added_files = list(set(second_scan) - set(initial_scan)) removed_files = list(set(initial_scan) - set(second_scan)) modified_files = [path for path in set(initial_scan) & set(second_scan) if initial_scan[path] != second_scan[path]] + return {'added': added_files, 'removed': removed_files, 'modified': modified_files} @staticmethod def perform_action_and_scan(inventory_path, callback) -> dict: + """ + Performs an action (callback) and scans pre and post action + + Args: + inventory_path: host's inventory path + callback (callback): callback + + + Returns: + returns a dictionary that contains the changes between the pre and the post scan + """ os_type = HostInformation.get_os_type(inventory_path) directories = ['/boot', '/usr/bin', '/root', '/usr/sbin'] @@ -519,9 +537,7 @@ def isComponentActive(inventory_path, host_role) -> bool: Returns: bool: True/False """ - return 'active' == Executor.execute_command(inventory_path, f'systemctl is-active {host_role}') - - + return 'active' == Executor.execute_command(inventory_path, f'systemctl is-active {host_role}').replace("\n", "") class Waits: diff --git a/deployability/modules/testing/tests/helpers/manager.py b/deployability/modules/testing/tests/helpers/manager.py index f7b4b9a181..5cf1010ec0 100644 --- a/deployability/modules/testing/tests/helpers/manager.py +++ b/deployability/modules/testing/tests/helpers/manager.py @@ -1,7 +1,9 @@ import requests -from .generic import HostInformation, HostConfiguration + +from .constants import CLUSTER_CONTROL, AGENT_CONTROL, WAZUH_CONF, WAZUH_ROOT from .executor import Executor, WazuhAPI -from .constants import WAZUH_CONTROL, CLUSTER_CONTROL, AGENT_CONTROL, CLIENT_KEYS, WAZUH_CONF, WAZUH_ROOT +from .generic import HostInformation, CheckFiles + class WazuhManager: @@ -93,6 +95,89 @@ def uninstall_managers(inventories_paths=[]) -> None: WazuhManager.uninstall_manager(inventory) + @staticmethod + def install_manager_callback(wazuh_params, manager_name, manager_params): + WazuhManager.install_manager(manager_params, manager_name, wazuh_params['wazuh_version']) + + + @staticmethod + def uninstall_manager_callback(manager_params): + WazuhManager.uninstall_manager(manager_params) + + + @staticmethod + def perform_action_and_scan(manager_params, action_callback): + result = CheckFiles.perform_action_and_scan(manager_params, action_callback) + os_name = HostInformation.get_os_name_from_inventory(manager_params) + if 'debian' in os_name: + filter_data = { + '/boot': {'added': [], 'removed': [], 'modified': ['grubenv']}, + '/usr/bin': { + 'added': [ + 'unattended-upgrade', 'gapplication', 'add-apt-repository', 'gpg-wks-server', 'pkexec', 'gpgsplit', + 'watchgnupg', 'pinentry-curses', 'gpg-zip', 'gsettings', 'gpg-agent', 'gresource', 'gdbus', + 'gpg-connect-agent', 'gpgconf', 'gpgparsemail', 'lspgpot', 'pkaction', 'pkttyagent', 'pkmon', + 'dirmngr', 'kbxutil', 'migrate-pubring-from-classic-gpg', 'gpgcompose', 'pkcheck', 'gpgsm', 'gio', + 'pkcon', 'gpgtar', 'dirmngr-client', 'gpg', 'filebeat', 'gawk', 'curl', 'update-mime-database', + 'dh_installxmlcatalogs', 'appstreamcli', 'lspgpot' + ], + 'removed': [], + 'modified': [] + }, + '/root': {'added': ['trustdb.gpg'], 'removed': [], 'modified': []}, + '/usr/sbin': { + 'added': [ + 'update-catalog', 'applygnupgdefaults', 'addgnupghome', 'install-sgmlcatalog', 'update-xmlcatalog' + ], + 'removed': [], + 'modified': [] + } + } + else: + filter_data = { + '/boot': { + 'added': ['grub2', 'loader', 'vmlinuz', 'System.map', 'config-', 'initramfs'], + 'removed': [], + 'modified': ['grubenv'] + }, + '/usr/bin': {'added': ['filebeat'], 'removed': [], 'modified': []}, + '/root': {'added': ['trustdb.gpg'], 'removed': [], 'modified': []}, + '/usr/sbin': {'added': [], 'removed': [], 'modified': []} + } + + # Use of filters + for directory, changes in result.items(): + if directory in filter_data: + for change, files in changes.items(): + if change in filter_data[directory]: + result[directory][change] = [file for file in files if file.split('/')[-1] not in filter_data[directory][change]] + + return result + + @staticmethod + def perform_install_and_scan_for_manager(manager_params, manager_name, wazuh_params): + action_callback = lambda: WazuhManager.install_manager_callback(wazuh_params, manager_name, manager_params) + result = WazuhManager.perform_action_and_scan(manager_params, action_callback) + WazuhManager.assert_results(result) + + + @staticmethod + def perform_uninstall_and_scan_for_manager(manager_params): + action_callback = lambda: WazuhManager.uninstall_manager_callback(manager_params) + result = WazuhManager.perform_action_and_scan(manager_params, action_callback) + WazuhManager.assert_results(result) + + + @staticmethod + def assert_results(result): + categories = ['/root', '/usr/bin', '/usr/sbin', '/boot'] + actions = ['added', 'modified', 'removed'] + # Testing the results + for category in categories: + for action in actions: + assert result[category][action] == [] + + @staticmethod def get_cluster_info(inventory_path) -> None: """ @@ -158,6 +243,7 @@ def get_manager_version(wazuh_api: WazuhAPI) -> str: str: The version of the manager. """ response = requests.get(f"{wazuh_api.api_url}/?pretty=true", headers=wazuh_api.headers, verify=False) + return eval(response.text)['data']['api_version'] @@ -169,6 +255,7 @@ def get_manager_revision(wazuh_api: WazuhAPI) -> str: str: The revision of the manager. """ response = requests.get(f"{wazuh_api.api_url}/?pretty=true", headers=wazuh_api.headers, verify=False) + return eval(response.text)['data']['revision'] @@ -180,6 +267,7 @@ def get_manager_host_name(wazuh_api: WazuhAPI) -> str: str: The hostname of the manager. """ response = requests.get(f"{wazuh_api.api_url}/?pretty=true", headers=wazuh_api.headers, verify=False) + return eval(response.text)['data']['hostname'] @@ -191,6 +279,7 @@ def get_manager_nodes_status(wazuh_api: WazuhAPI) -> dict: Dict: The status of the manager's nodes. """ response = requests.get(f"{wazuh_api.api_url}/manager/status", headers=wazuh_api.headers, verify=False) + return eval(response.text)['data']['affected_items'][0] @@ -202,4 +291,5 @@ def get_manager_logs(wazuh_api: WazuhAPI) -> list: List: The logs of the manager. """ response = requests.get(f"{wazuh_api.api_url}/manager/logs", headers=wazuh_api.headers, verify=False) + return eval(response.text)['data']['affected_items'] diff --git a/deployability/modules/testing/tests/helpers/utils.py b/deployability/modules/testing/tests/helpers/utils.py new file mode 100644 index 0000000000..145b0e621b --- /dev/null +++ b/deployability/modules/testing/tests/helpers/utils.py @@ -0,0 +1,10 @@ +import boto3 +import yaml + +class Utils: + + @staticmethod + def extract_ansible_host(file_path): + with open(file_path, 'r') as yaml_file: + inventory_data = yaml.safe_load(yaml_file) + return inventory_data.get('ansible_host') diff --git a/deployability/modules/testing/tests/test_agent/test_install.py b/deployability/modules/testing/tests/test_agent/test_install.py index d32d215169..958aeffaa6 100644 --- a/deployability/modules/testing/tests/test_agent/test_install.py +++ b/deployability/modules/testing/tests/test_agent/test_install.py @@ -2,38 +2,9 @@ from ..helpers.manager import WazuhManager from ..helpers.agent import WazuhAgent -from ..helpers.generic import HostConfiguration, CheckFiles, HostInformation, GeneralComponentActions +from ..helpers.generic import HostConfiguration, HostInformation, GeneralComponentActions from ..helpers.constants import WAZUH_ROOT - -def install_agent_callback(wazuh_params, agent_name, agent_params): - WazuhAgent.install_agent(agent_params, agent_name, wazuh_params['wazuh_version'], wazuh_params['wazuh_revision'], wazuh_params['live']) - - -def perform_action_and_scan_for_agent(agent_params, agent_name, wazuh_params): - result = CheckFiles.perform_action_and_scan(agent_params, lambda: install_agent_callback(wazuh_params, agent_name, agent_params)) - categories = ['/root', '/usr/bin', '/usr/sbin', '/boot'] - actions = ['added', 'modified', 'removed'] - - # Selecting filter - os_name = HostInformation.get_os_name_from_inventory(agent_params) - if 'debian' in os_name: - filter_data= {'/boot': {'added': [], 'removed': [], 'modified': ['grubenv']}, '/usr/bin': {'added': ['unattended-upgrade', 'gapplication', 'add-apt-repository', 'gpg-wks-server', 'pkexec', 'gpgsplit', 'watchgnupg', 'pinentry-curses', 'gpg-zip', 'gsettings', 'gpg-agent', 'gresource', 'gdbus', 'gpg-connect-agent', 'gpgconf', 'gpgparsemail', 'lspgpot', 'pkaction', 'pkttyagent', 'pkmon', 'dirmngr', 'kbxutil', 'migrate-pubring-from-classic-gpg', 'gpgcompose', 'pkcheck', 'gpgsm', 'gio', 'pkcon', 'gpgtar', 'dirmngr-client', 'gpg', 'filebeat', 'gawk', 'curl', 'update-mime-database', 'dh_installxmlcatalogs', 'appstreamcli','lspgpot'], 'removed': [], 'modified': []}, '/root': {'added': ['trustdb.gpg'], 'removed': [], 'modified': []}, '/usr/sbin': {'added': ['update-catalog', 'applygnupgdefaults', 'addgnupghome', 'install-sgmlcatalog', 'update-xmlcatalog'], 'removed': [], 'modified': []}} - else: - filter_data = {'/boot': {'added': [], 'removed': [], 'modified': ['grubenv']}, '/usr/bin': {'added': ['filebeat'], 'removed': [], 'modified': []}, '/root': {'added': ['trustdb.gpg'], 'removed': [], 'modified': []}, '/usr/sbin': {'added': [], 'removed': [], 'modified': []}} - - # Use of filters - for directory, changes in result.items(): - if directory in filter_data: - for change, files in changes.items(): - if change in filter_data[directory]: - result[directory][change] = [file for file in files if file.split('/')[-1] not in filter_data[directory][change]] - - # Testing the results - for category in categories: - for action in actions: - assert result[category][action] == [] - @pytest.fixture def wazuh_params(request): wazuh_version = request.config.getoption('--wazuh_version') @@ -74,6 +45,7 @@ def setup_test_environment(wazuh_params): wazuh_params['agents'] = {key: value for key, value in targets_dict.items() if key.startswith('agent-')} def test_installation(wazuh_params): + # Disabling firewall for all managers for manager_name, manager_params in wazuh_params['managers'].items(): HostConfiguration.disable_firewall(manager_params) @@ -86,7 +58,7 @@ def test_installation(wazuh_params): # Agent installation for agent_names, agent_params in wazuh_params['agents'].items(): - perform_action_and_scan_for_agent(agent_params, agent_names, wazuh_params) + WazuhAgent.perform_install_and_scan_for_agent(agent_params, agent_names, wazuh_params) # Testing installation directory for agent in wazuh_params['agents'].values(): diff --git a/deployability/modules/testing/tests/test_agent/test_uninstall.py b/deployability/modules/testing/tests/test_agent/test_uninstall.py index 06acc245e3..1ff66240c8 100644 --- a/deployability/modules/testing/tests/test_agent/test_uninstall.py +++ b/deployability/modules/testing/tests/test_agent/test_uninstall.py @@ -2,38 +2,10 @@ from ..helpers.manager import WazuhManager, WazuhAPI from ..helpers.agent import WazuhAgent -from ..helpers.generic import CheckFiles, HostInformation, GeneralComponentActions, Waits +from ..helpers.generic import HostInformation, GeneralComponentActions, Waits from ..helpers.constants import WAZUH_ROOT -def uninstall_agent_callback(wazuh_params, agent_params): - WazuhAgent.uninstall_agent(agent_params, wazuh_params['wazuh_version'], wazuh_params['wazuh_revision']) - - -def perform_action_and_scan_for_agent(agent_params, wazuh_params): - result = CheckFiles.perform_action_and_scan(agent_params, lambda: uninstall_agent_callback(wazuh_params, agent_params)) - categories = ['/root', '/usr/bin', '/usr/sbin', '/boot'] - actions = ['added', 'modified', 'removed'] - - # Selecting filter - os_name = HostInformation.get_os_name_from_inventory(agent_params) - if 'debian' in os_name: - filter_data= {'/boot': {'added': [], 'removed': [], 'modified': ['grubenv']}, '/usr/bin': {'added': ['unattended-upgrade', 'gapplication', 'add-apt-repository', 'gpg-wks-server', 'pkexec', 'gpgsplit', 'watchgnupg', 'pinentry-curses', 'gpg-zip', 'gsettings', 'gpg-agent', 'gresource', 'gdbus', 'gpg-connect-agent', 'gpgconf', 'gpgparsemail', 'lspgpot', 'pkaction', 'pkttyagent', 'pkmon', 'dirmngr', 'kbxutil', 'migrate-pubring-from-classic-gpg', 'gpgcompose', 'pkcheck', 'gpgsm', 'gio', 'pkcon', 'gpgtar', 'dirmngr-client', 'gpg', 'filebeat', 'gawk', 'curl', 'update-mime-database', 'dh_installxmlcatalogs', 'appstreamcli','lspgpot'], 'removed': [], 'modified': []}, '/root': {'added': ['trustdb.gpg'], 'removed': [], 'modified': []}, '/usr/sbin': {'added': ['update-catalog', 'applygnupgdefaults', 'addgnupghome', 'install-sgmlcatalog', 'update-xmlcatalog'], 'removed': [], 'modified': []}} - else: - filter_data = {'/boot': {'added': ['grub2', 'loader', 'vmlinuz', 'System.map', 'config-', 'initramfs'], 'removed': [], 'modified': ['grubenv']}, '/usr/bin': {'added': ['filebeat'], 'removed': [], 'modified': []}, '/root': {'added': ['trustdb.gpg'], 'removed': [], 'modified': []}, '/usr/sbin': {'added': [], 'removed': [], 'modified': []}} - - # Use of filters - for directory, changes in result.items(): - if directory in filter_data: - for change, files in changes.items(): - if change in filter_data[directory]: - result[directory][change] = [file for file in files if file.split('/')[-1] not in filter_data[directory][change]] - - # Testing the results - for category in categories: - for action in actions: - assert result[category][action] == [] - @pytest.fixture def wazuh_params(request): wazuh_version = request.config.getoption('--wazuh_version') @@ -79,7 +51,7 @@ def test_uninstall(wazuh_params): # Agent installation for agent_names, agent_params in wazuh_params['agents'].items(): - perform_action_and_scan_for_agent(agent_params,wazuh_params) + WazuhAgent.perform_uninstall_and_scan_for_agent(agent_params,wazuh_params) # Manager uninstallation status check for agent_names, agent_params in wazuh_params['agents'].items():