From 3a1f91559e3e67079333f9162c87a918e5e8ac98 Mon Sep 17 00:00:00 2001 From: Antonio Date: Thu, 18 Apr 2024 16:06:49 +0200 Subject: [PATCH] enhancement(#5229): macOS tests added --- deployability/modules/generic/ansible.py | 10 +- .../modules/testing/tests/helpers/agent.py | 116 ++++++++------ .../modules/testing/tests/helpers/executor.py | 54 ++++--- .../modules/testing/tests/helpers/generic.py | 143 ++++++++++++++---- .../modules/testing/tests/helpers/utils.py | 69 ++++++--- .../testing/tests/test_agent/test_install.py | 11 +- .../tests/test_agent/test_registration.py | 4 +- .../testing/tests/test_agent/test_restart.py | 2 +- .../testing/tests/test_agent/test_stop.py | 3 +- .../tests/test_agent/test_uninstall.py | 8 +- 10 files changed, 288 insertions(+), 132 deletions(-) diff --git a/deployability/modules/generic/ansible.py b/deployability/modules/generic/ansible.py index 6eaf083418..7767fc4551 100755 --- a/deployability/modules/generic/ansible.py +++ b/deployability/modules/generic/ansible.py @@ -1,8 +1,10 @@ # Copyright (C) 2015, Wazuh Inc. # Created by Wazuh, Inc. . # This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2 + import ansible_runner import jinja2 +from typing import Optional import yaml from pathlib import Path @@ -16,7 +18,8 @@ class Inventory(BaseModel): ansible_host: str | IPvAnyAddress ansible_user: str ansible_port: int - ansible_ssh_private_key_file: str + ansible_ssh_private_key_file: Optional[str] = None + ansible_password: Optional[str] = None class Ansible: @@ -118,10 +121,13 @@ def generate_inventory(self) -> dict: self.ansible_data.ansible_host: { 'ansible_port': self.ansible_data.ansible_port, 'ansible_user': self.ansible_data.ansible_user, - 'ansible_ssh_private_key_file': self.ansible_data.ansible_ssh_private_key_file + **({'ansible_ssh_private_key_file': self.ansible_data.ansible_ssh_private_key_file} + if hasattr(self.ansible_data, 'ansible_ssh_private_key_file') + else {'ansible_password': self.ansible_data.ansible_password}) } } } } + return inventory_data diff --git a/deployability/modules/testing/tests/helpers/agent.py b/deployability/modules/testing/tests/helpers/agent.py index 7e60c5fbca..fdb1d2a869 100644 --- a/deployability/modules/testing/tests/helpers/agent.py +++ b/deployability/modules/testing/tests/helpers/agent.py @@ -23,6 +23,7 @@ def install_agent(inventory_path, agent_name, wazuh_version, wazuh_revision, liv release = 'pre-release' os_type = HostInformation.get_os_type(inventory_path) + architecture = HostInformation.get_architecture(inventory_path) commands = [] if 'linux' in os_type: distribution = HostInformation.get_linux_distribution(inventory_path) @@ -64,11 +65,11 @@ def install_agent(inventory_path, agent_name, wazuh_version, wazuh_revision, liv "NET STATUS WazuhSvc" ]) elif 'macos' in os_type: - if 'intel' in architecture: + if 'x86_64' in architecture: commands.extend([ f'curl -so wazuh-agent.pkg https://{s3_url}.wazuh.com/{release}/macos/wazuh-agent-{wazuh_version}-1.intel64.pkg && echo "WAZUH_MANAGER=\'MANAGER_IP\' && WAZUH_AGENT_NAME=\'{agent_name}\'" > /tmp/wazuh_envs && sudo installer -pkg ./wazuh-agent.pkg -target /' ]) - elif 'apple' in architecture: + elif 'arm64' in architecture: commands.extend([ f'curl -so wazuh-agent.pkg https://{s3_url}.wazuh.com/{release}/macos/wazuh-agent-{wazuh_version}-1.arm64.pkg && echo "WAZUH_MANAGER=\'MANAGER_IP\' && WAZUH_AGENT_NAME=\'{agent_name}\'" > /tmp/wazuh_envs && sudo installer -pkg ./wazuh-agent.pkg -target /' ]) @@ -95,15 +96,23 @@ def register_agent(inventory_path, manager_path): 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>/
{internal_ip}<\/address>/g' {WAZUH_CONF}", - "systemctl restart wazuh-agent" - ] - - Executor.execute_commands(inventory_path, commands) - assert internal_ip in Executor.execute_command(inventory_path, f'cat {WAZUH_CONF}'), logger.error(f'Error configuring the Manager IP ({internal_ip})in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)} agent') + if 'linux' in inventory_path: + host_ip = HostInformation.get_internal_ip_from_aws_dns(host) if 'amazonaws' in host else host + commands = [ + f"sed -i 's/
MANAGER_IP<\/address>/
{host_ip}<\/address>/g' {WAZUH_CONF}", + "systemctl restart wazuh-agent" + ] + Executor.execute_commands(inventory_path, commands) + assert host_ip in Executor.execute_command(inventory_path, f'cat {WAZUH_CONF}'), logger.error(f'Error configuring the Manager IP ({host_ip}) in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)} agent') + + elif 'macos' in inventory_path: + host_ip = HostInformation.get_public_ip_from_aws_dns(host) if 'amazonaws' in host else host + commands = [ + f"sed -i '.bak' 's/
MANAGER_IP<\/address>/
{host_ip}<\/address>/g' /Library/Ossec/etc/ossec.conf", + "/Library/Ossec/bin/wazuh-control restart" + ] + Executor.execute_commands(inventory_path, commands) + assert host_ip in Executor.execute_command(inventory_path, f'cat /Library/Ossec/etc/ossec.conf'), logger.error(f'Error configuring the Manager IP ({host_ip}) in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)} agent') @staticmethod @@ -190,41 +199,47 @@ def perform_action_and_scan(agent_params, action_callback) -> dict: os_name = HostInformation.get_os_name_from_inventory(agent_params) logger.info(f'Applying filters in checkfiles in {HostInformation.get_os_name_and_version_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', 'symcryptrun' - ], - 'removed': [], - 'modified': [] - }, - '/root': {'added': ['trustdb.gpg', 'lesshst'], 'removed': [], 'modified': []}, - '/usr/sbin': { - 'added': [ - 'update-catalog', 'applygnupgdefaults', 'addgnupghome', 'install-sgmlcatalog', 'update-xmlcatalog' - ], - 'removed': [], - 'modified': [] + if 'linux' in 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', 'symcryptrun' + ], + 'removed': [], + 'modified': [] + }, + '/root': {'added': ['trustdb.gpg', 'lesshst'], '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', 'lesshst'], 'removed': [], 'modified': []}, + '/usr/sbin': {'added': [], 'removed': [], 'modified': []} + } + elif 'macos' in agent_params: + filter_data = { + '/usr/bin': {'added': [], 'removed': [], 'modified': []}, + '/usr/sbin': {'added': [], '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', 'lesshst'], 'removed': [], 'modified': []}, - '/usr/sbin': {'added': [], 'removed': [], 'modified': []} - } # Use of filters for directory, changes in result.items(): @@ -248,7 +263,7 @@ 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) logger.info(f'Pre and post install checkfile comparison in {HostInformation.get_os_name_and_version_from_inventory(agent_params)}: {result}') - WazuhAgent.assert_results(result) + WazuhAgent.assert_results(result, agent_params) @staticmethod @@ -264,11 +279,11 @@ def perform_uninstall_and_scan_for_agent(agent_params, wazuh_params) -> None: action_callback = lambda: WazuhAgent._uninstall_agent_callback(wazuh_params, agent_params) result = WazuhAgent.perform_action_and_scan(agent_params, action_callback) logger.info(f'Pre and post uninstall checkfile comparison in {HostInformation.get_os_name_and_version_from_inventory(agent_params)}: {result}') - WazuhAgent.assert_results(result) + WazuhAgent.assert_results(result, agent_params) @staticmethod - def assert_results(result) -> None: + def assert_results(result, agent_params) -> None: """ Gets the status of an agent given its name. @@ -276,7 +291,10 @@ def assert_results(result) -> None: result (dict): result of comparison between pre and post action scan """ - categories = ['/root', '/usr/bin', '/usr/sbin', '/boot'] + if 'linux' in agent_params: + categories = ['/root', '/usr/bin', '/usr/sbin', '/boot'] + elif 'macos' in agent_params: + categories = ['/usr/bin', '/usr/sbin'] actions = ['added', 'modified', 'removed'] # Testing the results for category in categories: diff --git a/deployability/modules/testing/tests/helpers/executor.py b/deployability/modules/testing/tests/helpers/executor.py index 6d204d6ba8..f77c924c05 100644 --- a/deployability/modules/testing/tests/helpers/executor.py +++ b/deployability/modules/testing/tests/helpers/executor.py @@ -3,6 +3,7 @@ # This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2 import json +import paramiko import requests import subprocess import urllib3 @@ -19,24 +20,41 @@ def execute_command(inventory_path, command) -> str: with open(inventory_path, 'r') as yaml_file: inventory_data = yaml.safe_load(yaml_file) - host = inventory_data.get('ansible_host') - port = inventory_data.get('ansible_port') - private_key_path = inventory_data.get('ansible_ssh_private_key_file') - username = inventory_data.get('ansible_user') - - ssh_command = [ - "ssh", - "-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 + if 'ansible_ssh_private_key_file' in inventory_data: + host = inventory_data.get('ansible_host') + port = inventory_data.get('ansible_port') + private_key_path = inventory_data.get('ansible_ssh_private_key_file') + username = inventory_data.get('ansible_user') + + ssh_command = [ + "ssh", + "-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 + else: + host = inventory_data.get('ansible_host', None) + port = inventory_data.get('ansible_port', 22) + user = inventory_data.get('ansible_user', None) + password = inventory_data.get('ansible_password', None) + + ssh_client = paramiko.SSHClient() + ssh_client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + ssh_client.connect(hostname=host, port=port, username=user, password=password) + stdin, stdout, stderr = ssh_client.exec_command(f"sudo {command}") + + result = ''.join(stdout.readlines()) + + ssh_client.close() + + return result @staticmethod diff --git a/deployability/modules/testing/tests/helpers/generic.py b/deployability/modules/testing/tests/helpers/generic.py index 25308fd2cc..6efb3d6794 100644 --- a/deployability/modules/testing/tests/helpers/generic.py +++ b/deployability/modules/testing/tests/helpers/generic.py @@ -32,7 +32,11 @@ def dir_exists(inventory_path, dir_path) -> str: Returns: bool: True or False """ - return 'true' in Executor.execute_command(inventory_path, f'test -d {dir_path} && echo "true" || echo "false"') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return 'true' in Executor.execute_command(inventory_path, f'test -d {dir_path} && echo "true" || echo "false"') + elif os_type == 'macos': + return 'true' in Executor.execute_command(inventory_path, f'stat {dir_path} >/dev/null 2>&1 && echo "true" || echo "false"') @staticmethod @@ -47,7 +51,11 @@ def file_exists(inventory_path, file_path) -> bool: Returns: bool: True or False """ - return 'true' in Executor.execute_command(inventory_path, f'test -f {file_path} && echo "true" || echo "false"') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return 'true' in Executor.execute_command(inventory_path, f'test -f {file_path} && echo "true" || echo "false"') + elif os_type == 'macos': + return 'true' in Executor.execute_command(inventory_path, f'stat {file_path} >/dev/null 2>&1 && echo "true" || echo "false"') @staticmethod @@ -61,8 +69,16 @@ def get_os_type(inventory_path) -> str: Returns: str: type of host (windows, linux, macos) """ - system = Executor.execute_command(inventory_path, 'uname') - return system.lower() + if 'manager' in inventory_path: + pattern = r'manager-(\w+)-' + elif 'agent' in inventory_path: + pattern = r'agent-(\w+)-' + result = re.search(pattern, inventory_path) + if result: + return result.group(1) + else: + return None + @staticmethod @@ -115,9 +131,9 @@ def get_os_name_from_inventory(inventory_path) -> str: str: linux os name (debian, ubuntu, opensuse, amazon, centos, redhat) """ if 'manager' in inventory_path: - os_name = re.search(r'/manager-linux-([^-]+)-', inventory_path).group(1) + os_name = re.search(r'/manager-[^-]+-([^-]+)-', inventory_path).group(1) elif 'agent' in inventory_path: - os_name = re.search(r'/agent-linux-([^-]+)-', inventory_path).group(1) + os_name = re.search(r'/agent-[^-]+-([^-]+)-', inventory_path).group(1) return os_name @@ -133,9 +149,9 @@ def get_os_name_and_version_from_inventory(inventory_path) -> tuple: tuple: linux os name and version (e.g., ('ubuntu', '22.04')) """ if 'manager' in inventory_path: - match = re.search(r'/manager-linux-([^-]+)-([^-]+)-', inventory_path) + match = re.search(r'/manager-[^-]+-([^-]+)-([^-]+)-', inventory_path) elif 'agent' in inventory_path: - match = re.search(r'/agent-linux-([^-]+)-([^-]+)-', inventory_path) + match = re.search(r'/agent-[^-]+-([^-]+)-([^-]+)-', inventory_path) if match: os_name = match.group(1) version = match.group(2) @@ -158,7 +174,7 @@ def get_current_dir(inventory_path) -> str: return Executor.execute_command(inventory_path, 'pwd').replace("\n","") @staticmethod - def get_internal_ip_from_aws_dns(dns_name): + def get_internal_ip_from_aws_dns(dns_name) -> str: """ It returns the private AWS IP from dns_name @@ -176,6 +192,24 @@ def get_internal_ip_from_aws_dns(dns_name): else: return None + @staticmethod + def get_public_ip_from_aws_dns(dns_name) -> str: + """ + It returns the public AWS IP from dns_name + + Args: + dns_name (str): host's dns public dns name + + Returns: + str: public ip + """ + try: + ip_address = socket.gethostbyname(dns_name) + return ip_address + except socket.gaierror as e: + logger.error("Error obtaining IP address:", e) + return None + @staticmethod def get_client_keys(inventory_path) -> list[dict]: """ @@ -188,7 +222,11 @@ def get_client_keys(inventory_path) -> list[dict]: list: List of dictionaries with the client keys. """ clients = [] - client_key = Executor.execute_command(inventory_path, f'cat {CLIENT_KEYS}') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + client_key = Executor.execute_command(inventory_path, f'cat {CLIENT_KEYS}') + elif os_type == 'macos': + client_key = Executor.execute_command(inventory_path, f'cat /Library/Ossec/etc/client.keys') lines = client_key.split('\n')[:-1] for line in lines: _id, name, address, password = line.strip().split() @@ -227,14 +265,18 @@ def disable_firewall(inventory_path) -> None: inventory_path: host's inventory path """ - commands = ["sudo systemctl stop firewalld", "sudo systemctl disable firewalld"] - if GeneralComponentActions.isComponentActive(inventory_path, 'firewalld'): - Executor.execute_commands(inventory_path, commands) + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + commands = ["sudo systemctl stop firewalld", "sudo systemctl disable firewalld"] + if GeneralComponentActions.isComponentActive(inventory_path, 'firewalld'): + Executor.execute_commands(inventory_path, commands) + logger.info(f'Firewall disabled on {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') + else: + logger.info(f'No Firewall to disable on {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') + elif os_type == 'macos': logger.info(f'Firewall disabled on {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - else: - logger.info(f'No Firewall to disable on {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - + Executor.execute_command(inventory_path, 'sudo pfctl -d') def _extract_hosts(paths, is_aws): @@ -457,10 +499,12 @@ def _checkfiles(inventory_path, os_type, directory, filter= None, hash_algorithm Returns: Dict: dict of directories:hash """ - if 'linux' in os_type or 'macos' in os_type: + if 'linux' in os_type: command = f'sudo find {directory} -type f -exec sha256sum {{}} + {filter}' result = Executor.execute_command(inventory_path, command) - + elif 'macos' in os_type: + command = f'sudo find {directory} -type f -exec shasum -a 256 {{}} \; {filter}' + result = Executor.execute_command(inventory_path, command) elif 'windows' in os_type: command = 'dir /a-d /b /s | findstr /v /c:"\\.$" /c:"\\..$"| find /c ":"' else: @@ -504,9 +548,15 @@ def perform_action_and_scan(inventory_path, callback) -> dict: """ os_type = HostInformation.get_os_type(inventory_path) - directories = ['/boot', '/usr/bin', '/root', '/usr/sbin'] - filters_keywords = ['grep', 'tar', 'coreutils', 'sed', 'procps', 'gawk', 'lsof', 'curl', 'openssl', 'libcap', 'apt-transport-https', 'libcap2-bin', 'software-properties-common', 'gnupg', 'gpg'] - filters = f"| grep -v {filters_keywords[0]}" + if 'linux' in inventory_path: + directories = ['/boot', '/usr/bin', '/root', '/usr/sbin'] + filters_keywords = ['grep', 'tar', 'coreutils', 'sed', 'procps', 'gawk', 'lsof', 'curl', 'openssl', 'libcap', 'apt-transport-https', 'libcap2-bin', 'software-properties-common', 'gnupg', 'gpg'] + filters = f"| grep -v {filters_keywords[0]}" + + elif 'macos' in inventory_path: + directories = ['/usr/bin', '/usr/sbin'] + filters_keywords = ['grep'] + filters = f"| grep -v {filters_keywords[0]}" for filter_ in filters_keywords[1:]: filters+= f" | grep -v {filter_}" @@ -533,8 +583,11 @@ def get_component_status(inventory_path, host_role) -> str: str: Role status """ logger.info(f'Getting status of {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - - return Executor.execute_command(inventory_path, f'systemctl status {host_role}') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return Executor.execute_command(inventory_path, f'systemctl status {host_role}') + elif os_type == 'macos': + return Executor.execute_command(inventory_path, f'/Library/Ossec/bin/wazuh-control status | grep {host_role}') @staticmethod @@ -546,9 +599,12 @@ def component_stop(inventory_path, host_role) -> None: inventory_path: host's inventory path host_role: role of the component """ - logger.info(f'Stopping {host_role} in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - Executor.execute_command(inventory_path, f'systemctl stop {host_role}') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return Executor.execute_command(inventory_path, f'systemctl stop {host_role}') + elif os_type == 'macos': + return Executor.execute_command(inventory_path, f'/Library/Ossec/bin/wazuh-control stop | grep {host_role}') @staticmethod @@ -562,7 +618,11 @@ def component_restart(inventory_path, host_role) -> None: """ logger.info(f'Restarting {host_role} in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - Executor.execute_command(inventory_path, f'systemctl restart {host_role}') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return Executor.execute_command(inventory_path, f'systemctl restart {host_role}') + elif os_type == 'macos': + return Executor.execute_command(inventory_path, f'/Library/Ossec/bin/wazuh-control restart | grep {host_role}') @staticmethod @@ -576,7 +636,11 @@ def component_start(inventory_path, host_role) -> None: """ logger.info(f'Starting {host_role} in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - Executor.execute_command(inventory_path, f'systemctl restart {host_role}') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return Executor.execute_command(inventory_path, f'systemctl start {host_role}') + elif os_type == 'macos': + return Executor.execute_command(inventory_path, f'/Library/Ossec/bin/wazuh-control start | grep {host_role}') @staticmethod @@ -590,7 +654,11 @@ def get_component_version(inventory_path) -> str: Returns: str: version """ - return Executor.execute_command(inventory_path, f'{WAZUH_CONTROL} info -v') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return Executor.execute_command(inventory_path, f'{WAZUH_CONTROL} info -v') + elif os_type == 'macos': + return Executor.execute_command(inventory_path, f'/Library/Ossec/bin/wazuh-control info -v') @staticmethod @@ -604,7 +672,11 @@ def get_component_revision(inventory_path) -> str: Returns: str: revision number """ - return Executor.execute_command(inventory_path, f'{WAZUH_CONTROL} info -r') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return Executor.execute_command(inventory_path, f'{WAZUH_CONTROL} info -r') + elif os_type == 'macos': + return Executor.execute_command(inventory_path, f'/Library/Ossec/bin/wazuh-control info -r') @staticmethod @@ -618,7 +690,11 @@ def hasAgentClientKeys(inventory_path) -> bool: Returns: bool: True/False """ - return 'true' in Executor.execute_command(inventory_path, f'[ -f {CLIENT_KEYS} ] && echo true || echo false') + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return HostInformation.file_exists(inventory_path,{CLIENT_KEYS}) + elif os_type == 'macos': + return HostInformation.file_exists(inventory_path, '/Library/Ossec/etc/client.keys') @staticmethod @@ -633,7 +709,12 @@ def isComponentActive(inventory_path, host_role) -> bool: Returns: bool: True/False """ - return 'active' == Executor.execute_command(inventory_path, f'systemctl is-active {host_role}').replace("\n", "") + os_type = HostInformation.get_os_type(inventory_path) + if os_type == 'linux': + return 'active' == Executor.execute_command(inventory_path, f'systemctl is-active {host_role}').replace("\n", "") + elif os_type == 'macos': + return f'com.{host_role.replace("-", ".")}' in Executor.execute_command(inventory_path, f'launchctl list | grep com.{host_role.replace("-", ".")}') + class Waits: diff --git a/deployability/modules/testing/tests/helpers/utils.py b/deployability/modules/testing/tests/helpers/utils.py index 211ee78b8f..5b7c4068fd 100644 --- a/deployability/modules/testing/tests/helpers/utils.py +++ b/deployability/modules/testing/tests/helpers/utils.py @@ -26,9 +26,9 @@ def extract_ansible_host(file_path) -> str: @staticmethod def check_inventory_connection(inventory_path, attempts=10, sleep=30) -> bool: if 'manager' in inventory_path: - match = re.search(r'/manager-linux-([^-]+)-([^-]+)-', inventory_path) + match = re.search(r'/manager-[^-]+-([^-]+)-([^-]+)-', inventory_path) elif 'agent' in inventory_path: - match = re.search(r'/agent-linux-([^-]+)-([^-]+)-', inventory_path) + match = re.search(r'/agent-[^-]+-([^-]+)-([^-]+)-', inventory_path) if match: os_name = match.group(1)+ '-' + match.group(2) logger.info(f'Checking connection to {os_name}') @@ -40,30 +40,55 @@ def check_inventory_connection(inventory_path, attempts=10, sleep=30) -> bool: except yaml.YAMLError: raise ValueError(logger.error(f'Invalid inventory information in {os_name}')) - host = inventory_data.get('ansible_host') - port = inventory_data.get('ansible_port') - private_key_path = inventory_data.get('ansible_ssh_private_key_file') - username = inventory_data.get('ansible_user') - - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - private_key = paramiko.RSAKey.from_private_key_file(private_key_path) + + if 'ansible_ssh_private_key_file' in inventory_data: + host = inventory_data.get('ansible_host') + port = inventory_data.get('ansible_port') + private_key_path = inventory_data.get('ansible_ssh_private_key_file') + username = inventory_data.get('ansible_user') - for attempt in range(1, attempts + 1): ssh = paramiko.SSHClient() ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) private_key = paramiko.RSAKey.from_private_key_file(private_key_path) - try: - ssh.connect(hostname=host, port=port, username=username, pkey=private_key) - logger.info(f'Connection established successfully in {os_name}') - ssh.close() - return True - except paramiko.AuthenticationException: - logger.error(f'Authentication error. Check SSH credentials in {os_name}') - return False - except Exception as e: - logger.warning(f'Error on attempt {attempt} of {attempts}: {e}') - time.sleep(sleep) + + for attempt in range(1, attempts + 1): + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + private_key = paramiko.RSAKey.from_private_key_file(private_key_path) + try: + ssh.connect(hostname=host, port=port, username=username, pkey=private_key) + logger.info(f'Connection established successfully in {os_name}') + ssh.close() + return True + except paramiko.AuthenticationException: + logger.error(f'Authentication error. Check SSH credentials in {os_name}') + return False + except Exception as e: + logger.warning(f'Error on attempt {attempt} of {attempts}: {e}') + time.sleep(sleep) + else: + host = inventory_data.get('ansible_host', None) + port = inventory_data.get('ansible_port', 22) + user = inventory_data.get('ansible_user', None) + password = inventory_data.get('ansible_password', None) + + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + for attempt in range(1, attempts + 1): + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + ssh.connect(hostname=host, port=port, username=user, password=password) + logger.info(f'Connection established successfully in {os_name}') + ssh.close() + return True + except paramiko.AuthenticationException: + logger.error(f'Authentication error. Check SSH credentials in {os_name}') + return False + except Exception as e: + logger.warning(f'Error on attempt {attempt} of {attempts}: {e}') + time.sleep(sleep) logger.error(f'Connection attempts failed after {attempts} tries. Connection timeout in {os_name}') return False diff --git a/deployability/modules/testing/tests/test_agent/test_install.py b/deployability/modules/testing/tests/test_agent/test_install.py index 72a454bbef..5ba6001178 100644 --- a/deployability/modules/testing/tests/test_agent/test_install.py +++ b/deployability/modules/testing/tests/test_agent/test_install.py @@ -54,6 +54,7 @@ def setup_test_environment(wazuh_params): updated_agents = {} for agent_name, agent_params in wazuh_params['agents'].items(): Utils.check_inventory_connection(agent_params) + if GeneralComponentActions.isComponentActive(agent_params, 'wazuh-agent') and GeneralComponentActions.hasAgentClientKeys(agent_params): if HostInformation.get_client_keys(agent_params) != []: client_name = HostInformation.get_client_keys(agent_params)[0]['name'] @@ -84,15 +85,19 @@ def test_installation(wazuh_params): for agent_names, agent_params in wazuh_params['agents'].items(): WazuhAgent.perform_install_and_scan_for_agent(agent_params, agent_names, wazuh_params) + # Testing installation directory - for agent in wazuh_params['agents'].values(): - assert HostInformation.dir_exists(agent, WAZUH_ROOT), logger.error(f'The {WAZUH_ROOT} is not present in {HostInformation.get_os_name_and_version_from_inventory(agent)}') + for agent_names, agent_params in wazuh_params['agents'].items(): + if HostInformation.get_os_type(agent_params) == 'linux': + assert HostInformation.dir_exists(agent_params, WAZUH_ROOT), logger.error(f'The {WAZUH_ROOT} is not present in {HostInformation.get_os_name_and_version_from_inventory(agent_params)}') + elif HostInformation.get_os_type(agent_params) == 'macos': + assert HostInformation.dir_exists(agent_params, '/Library/Ossec'), logger.error(f'The /Library/Ossec is not present in {HostInformation.get_os_name_and_version_from_inventory(agent_params)}') def test_status(wazuh_params): for agent in wazuh_params['agents'].values(): agent_status = GeneralComponentActions.get_component_status(agent, 'wazuh-agent') - assert 'loaded' in agent_status, logger.error(f'The {HostInformation.get_os_name_and_version_from_inventory(agent)} status is not loaded') + assert 'loaded' in agent_status or 'not running' in agent_status, logger.error(f'The {HostInformation.get_os_name_and_version_from_inventory(agent)} status is not loaded') def test_version(wazuh_params): diff --git a/deployability/modules/testing/tests/test_agent/test_registration.py b/deployability/modules/testing/tests/test_agent/test_registration.py index 3d15ea5e15..d3f97d4692 100644 --- a/deployability/modules/testing/tests/test_agent/test_registration.py +++ b/deployability/modules/testing/tests/test_agent/test_registration.py @@ -69,7 +69,7 @@ def test_registration(wazuh_params): def test_status(wazuh_params): for agent in wazuh_params['agents'].values(): - assert 'active' in GeneralComponentActions.get_component_status(agent, 'wazuh-agent'), logger.error(f'The {HostInformation.get_os_name_and_version_from_inventory(agent)} is not active') + assert 'active' in GeneralComponentActions.get_component_status(agent, 'wazuh-agent') or 'is running' in GeneralComponentActions.get_component_status(agent, 'wazuh-agent'), logger.error(f'The {HostInformation.get_os_name_and_version_from_inventory(agent)} is not active') def test_connection(wazuh_params): @@ -82,7 +82,7 @@ def test_connection(wazuh_params): def test_isActive(wazuh_params): wazuh_api = WazuhAPI(wazuh_params['master']) for agent_names, agent_params in wazuh_params['agents'].items(): - assert GeneralComponentActions.isComponentActive(agent_params, 'wazuh-agent'), logger.error(f'{agent_names} is not active by API') + assert GeneralComponentActions.isComponentActive(agent_params, 'wazuh-agent'), logger.error(f'{agent_names} is not active by command') expected_condition_func = lambda: 'active' == WazuhAgent.get_agent_status(wazuh_api, agent_names) Waits.dynamic_wait(expected_condition_func, cycles=20, waiting_time=30) diff --git a/deployability/modules/testing/tests/test_agent/test_restart.py b/deployability/modules/testing/tests/test_agent/test_restart.py index 5d7b4b5081..d0fcb8be56 100644 --- a/deployability/modules/testing/tests/test_agent/test_restart.py +++ b/deployability/modules/testing/tests/test_agent/test_restart.py @@ -68,7 +68,7 @@ def test_restart(wazuh_params): def test_status(wazuh_params): for agent_names, agent_params in wazuh_params['agents'].items(): - assert 'active' in GeneralComponentActions.get_component_status(agent_params, 'wazuh-agent'), logger.error(f'{agent_names} is not active by command') + assert 'active' in GeneralComponentActions.get_component_status(agent_params, 'wazuh-agent') or 'is running' in GeneralComponentActions.get_component_status(agent_params, 'wazuh-agent'), logger.error(f'{agent_names} is not active by command') def test_connection(wazuh_params): diff --git a/deployability/modules/testing/tests/test_agent/test_stop.py b/deployability/modules/testing/tests/test_agent/test_stop.py index 1463862510..c4b539b0fa 100644 --- a/deployability/modules/testing/tests/test_agent/test_stop.py +++ b/deployability/modules/testing/tests/test_agent/test_stop.py @@ -71,8 +71,7 @@ def test_stop(wazuh_params): GeneralComponentActions.component_stop(agent_params, 'wazuh-agent') for agent_names, agent_params in wazuh_params['agents'].items(): - assert 'inactive' in GeneralComponentActions.get_component_status(agent_params, 'wazuh-agent'), logger.error(f'{agent_names} is still active by command') - assert not GeneralComponentActions.isComponentActive(agent_params, 'wazuh-agent'), logger.error(f'{agent_names} is still active by command') + assert 'inactive' in GeneralComponentActions.get_component_status(agent_params, 'wazuh-agent') or 'not running' in GeneralComponentActions.get_component_status(agent_params, 'wazuh-agent'), logger.error(f'{agent_names} is still active by command') expected_condition_func = lambda: 'disconnected' == WazuhAgent.get_agent_status(wazuh_api, agent_names) Waits.dynamic_wait(expected_condition_func, cycles=20, waiting_time=30) diff --git a/deployability/modules/testing/tests/test_agent/test_uninstall.py b/deployability/modules/testing/tests/test_agent/test_uninstall.py index a5d44ac154..e4375b3ebb 100644 --- a/deployability/modules/testing/tests/test_agent/test_uninstall.py +++ b/deployability/modules/testing/tests/test_agent/test_uninstall.py @@ -66,12 +66,16 @@ def setup_test_environment(wazuh_params): def test_uninstall(wazuh_params): for agent_names, agent_params in wazuh_params['agents'].items(): assert GeneralComponentActions.isComponentActive(agent_params, 'wazuh-agent'), logger.error(f'{agent_names} is not Active before the installation') - assert HostInformation.dir_exists(agent_params, WAZUH_ROOT), logger.error(f'The {WAZUH_ROOT} is not present in the host {agent_names}') + if 'linux' in agent_params: + assert HostInformation.dir_exists(agent_params, WAZUH_ROOT), logger.error(f'The {WAZUH_ROOT} is not present in the host {agent_names}') + elif 'macos' in wazuh_params: + assert HostInformation.dir_exists(agent_params, '/Library/Ossec'), logger.error(f'The /Library/Ossec is not present in the host {agent_names}') - # Agent installation + # Agent uninstallation for agent_names, agent_params in wazuh_params['agents'].items(): 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(): assert 'Disconnected' in WazuhManager.get_agent_control_info(wazuh_params['master']), logger.error(f'{agent_names} is still connected in the Manager')