diff --git a/.gitignore b/.gitignore index 9518d0ec7d..441a732a6f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,8 +3,9 @@ __pycache__ .pytest_cache venv -wazuh_testing.egg-info +*.egg-info dist +build # Python bytecode files *.pyc diff --git a/deployability/deps/requirements.txt b/deployability/deps/requirements.txt index 5213aad50d..b71cb57648 100755 --- a/deployability/deps/requirements.txt +++ b/deployability/deps/requirements.txt @@ -12,4 +12,4 @@ pytest==7.4.4 paramiko==3.4.0 requests==2.31.0 chardet==5.2.0 -pywinrm==0.3.0 +pywinrm==0.4.0 diff --git a/deployability/modules/generic/ansible.py b/deployability/modules/generic/ansible.py index 0829c84b3e..d13aacac7a 100755 --- a/deployability/modules/generic/ansible.py +++ b/deployability/modules/generic/ansible.py @@ -7,6 +7,7 @@ from pathlib import Path from pydantic import BaseModel, IPvAnyAddress +from typing import Optional from modules.generic.utils import Utils from modules.generic.logger import Logger @@ -16,7 +17,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,7 +120,9 @@ 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}) } } } diff --git a/deployability/modules/testing/tests/helpers/agent.py b/deployability/modules/testing/tests/helpers/agent.py index 3815bbd4ea..0714d648a8 100644 --- a/deployability/modules/testing/tests/helpers/agent.py +++ b/deployability/modules/testing/tests/helpers/agent.py @@ -5,8 +5,8 @@ import yaml from typing import List, Optional -from .constants import WAZUH_CONF, WAZUH_ROOT -from .executor import Executor, WazuhAPI +from .constants import WAZUH_CONF, WAZUH_ROOT, WAZUH_WINDOWS_CONF +from .executor import Executor, WazuhAPI, ConnectionManager from .generic import HostInformation, CheckFiles from modules.testing.utils import logger @@ -15,32 +15,33 @@ class WazuhAgent: @staticmethod def install_agent(inventory_path, agent_name, wazuh_version, wazuh_revision, live) -> None: - if live == True: + if live: s3_url = 'packages' - release = wazuh_version[0:3] + release = wazuh_version[:1] + ".x" else: s3_url = 'packages-dev' release = 'pre-release' os_type = HostInformation.get_os_type(inventory_path) commands = [] + if 'linux' in os_type: distribution = HostInformation.get_linux_distribution(inventory_path) architecture = HostInformation.get_architecture(inventory_path) - if distribution == 'rpm' and 'x86_64' in architecture: + if distribution == 'rpm' and 'amd64' in architecture: 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' and 'aarch64' in architecture: + elif distribution == 'rpm' and 'arm64' 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" ]) - elif distribution == 'deb' and 'x86_64' in architecture: + elif distribution == 'deb' and 'amd64' in architecture: commands.extend([ f"wget https://{s3_url}.wazuh.com/{release}/apt/pool/main/w/wazuh-agent/wazuh-agent_{wazuh_version}-1_amd64.deb && sudo WAZUH_MANAGER='MANAGER_IP' WAZUH_AGENT_NAME='{agent_name}' dpkg -i ./wazuh-agent_{wazuh_version}-1_amd64.deb" ]) - elif distribution == 'deb' and 'aarch64' in architecture: + elif distribution == 'deb' and 'arm64' in architecture: commands.extend([ f"wget https://{s3_url}.wazuh.com/{release}/apt/pool/main/w/wazuh-agent/wazuh-agent_{wazuh_version}-1_arm64.deb && sudo WAZUH_MANAGER='MANAGER_IP' WAZUH_AGENT_NAME='{agent_name}' dpkg -i ./wazuh-agent_{wazuh_version}-1arm64.deb" ]) @@ -54,15 +55,16 @@ def install_agent(inventory_path, agent_name, wazuh_version, wazuh_revision, liv commands.extend(system_commands) elif 'windows' in os_type : commands.extend([ - f"Invoke-WebRequest -Uri https://packages.wazuh.com/{release}/windows/wazuh-agent-{wazuh_version}-1.msi" - "-OutFile ${env.tmp}\wazuh-agent;" - "msiexec.exe /i ${env.tmp}\wazuh-agent /q" - f"WAZUH_MANAGER='MANAGER_IP'" - f"WAZUH_AGENT_NAME='{agent_name}'" - f"WAZUH_REGISTRATION_SERVER='MANAGER_IP'", - "NET START WazuhSvc", - "NET STATUS WazuhSvc" - ]) + f"Invoke-WebRequest -Uri https://packages.wazuh.com/{release}/windows/wazuh-agent-{wazuh_version}-1.msi " + "-OutFile $env:TEMP\wazuh-agent.msi" + ]) + commands.extend([ + "msiexec.exe /i $env:TEMP\wazuh-agent.msi /q " + f"WAZUH_MANAGER='MANAGER_IP' " + f"WAZUH_AGENT_NAME='{agent_name}' " + f"WAZUH_REGISTRATION_SERVER='MANAGER_IP' " + ]) + commands.extend(["NET START WazuhSvc"]) elif 'macos' in os_type: if 'intel' in architecture: commands.extend([ @@ -79,7 +81,7 @@ def install_agent(inventory_path, agent_name, wazuh_version, wazuh_revision, liv commands.extend(system_commands) logger.info(f'Installing Agent in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - Executor.execute_commands(inventory_path, commands) + ConnectionManager.execute_commands(inventory_path, commands) @staticmethod @@ -97,24 +99,53 @@ def register_agent(inventory_path, manager_path): 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" - ] + os_type = HostInformation.get_os_type(inventory_path) + logger.info(f'os_type {os_type}') + + if 'linux' in os_type: + commands = [ + f"sed -i 's/
MANAGER_IP<\/address>/
{internal_ip}<\/address>/g' {WAZUH_CONF}", + "systemctl restart wazuh-agent" + ] + + ConnectionManager.execute_commands(inventory_path, commands) + result = ConnectionManager.execute_commands(inventory_path, f'cat {WAZUH_CONF}') + assert internal_ip in result.get('output'), logger.error(f"""Error configuring the Manager IP ({internal_ip})in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)} agent""") + elif 'windows' in os_type : + try: + commands = [ + f'(Get-Content -Path "{WAZUH_WINDOWS_CONF}" -Raw) -replace "
MANAGER_IP
", "
{internal_ip}
" | Set-Content -Path "{WAZUH_WINDOWS_CONF}"', + "NET START WazuhSvc" + ] - 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') + ConnectionManager.execute_commands(inventory_path, commands) + except Exception as e: + raise Exception(f'Error registering agent. Error executing: {commands} with error: {e}') + result = ConnectionManager.execute_commands(inventory_path, f'Get-Content "{WAZUH_WINDOWS_CONF}"') + assert internal_ip in result.get('output'), logger.error(f'Error configuring the Manager IP ({internal_ip})in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)} agent') @staticmethod def set_protocol_agent_connection(inventory_path, protocol): - commands = [ - f"sed -i 's/[^<]*<\/protocol>/{protocol}<\/protocol>/g' {WAZUH_CONF}", - "systemctl restart wazuh-agent" - ] + os_type = HostInformation.get_os_type(inventory_path) + + if 'linux' in os_type: + commands = [ + f"sed -i 's/[^<]*<\/protocol>/{protocol}<\/protocol>/g' {WAZUH_CONF}", + "systemctl restart wazuh-agent" + ] - Executor.execute_commands(inventory_path, commands) - assert protocol in Executor.execute_command(inventory_path, f'cat {WAZUH_CONF}'), logger.error(f'Error configuring the protocol ({protocol}) in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)} agent') + ConnectionManager.execute_commands(inventory_path, commands) + result = ConnectionManager.execute_commands(inventory_path, f'cat {WAZUH_CONF}') + assert protocol in result.get('output'), logger.error(f'Error configuring the protocol ({protocol}) in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)} agent') + elif 'windows' in os_type : + commands = [ + f"(Get-Content -Path '{WAZUH_WINDOWS_CONF}') -replace '[^<]*<\/protocol>', '{protocol}' | Set-Content -Path '{WAZUH_WINDOWS_CONF}'" + ] + + ConnectionManager.execute_commands(inventory_path, commands) + result = ConnectionManager.execute_commands(inventory_path, f'Get-Content -Path "{WAZUH_WINDOWS_CONF}"') + assert protocol in result.get('output'), logger.error(f'Error configuring the protocol ({protocol}) in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)} agent') @staticmethod @@ -150,7 +181,7 @@ def uninstall_agent(inventory_path, wazuh_version=None, wazuh_revision=None) -> commands.extend(system_commands) elif 'windows' in os_type: commands.extend([ - f"msiexec.exe /x wazuh-agent-{wazuh_version}-1.msi /qn" + f"msiexec.exe /x $env:TEMP\wazuh-agent.msi /qn" ]) elif 'macos' in os_type: commands.extend([ @@ -165,7 +196,7 @@ def uninstall_agent(inventory_path, wazuh_version=None, wazuh_revision=None) -> ]) logger.info(f'Uninstalling Agent in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - Executor.execute_commands(inventory_path, commands) + ConnectionManager.execute_commands(inventory_path, commands) @staticmethod @@ -188,7 +219,7 @@ def _uninstall_agent_callback(wazuh_params, agent_params): def perform_action_and_scan(agent_params, action_callback) -> dict: """ Takes scans using filters, the callback action and compares the result - + Args: agent_params (str): agent parameters callbak (cb): callback (action) @@ -250,7 +281,7 @@ def perform_action_and_scan(agent_params, action_callback) -> dict: def perform_install_and_scan_for_agent(agent_params, agent_name, wazuh_params) -> None: """ Coordinates the action of install the agent and compares the checkfiles - + Args: agent_params (str): agent parameters wazuh_params (str): wazuh parameters @@ -259,14 +290,14 @@ 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 def perform_uninstall_and_scan_for_agent(agent_params, wazuh_params) -> None: """ Coordinates the action of uninstall the agent and compares the checkfiles - + Args: agent_params (str): agent parameters wazuh_params (str): wazuh parameters @@ -275,23 +306,29 @@ 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, params = None) -> None: """ Gets the status of an agent given its name. - + Args: result (dict): result of comparison between pre and post action scan """ - categories = ['/root', '/usr/bin', '/usr/sbin', '/boot'] + os_type = HostInformation.get_os_type(params) + + if os_type == 'linux': + categories = ['/root', '/usr/bin', '/usr/sbin', '/boot'] + elif os_type == 'windows': + categories = ['C:\\Program Files', 'C:\\Program Files (x86)','C:\\Users\\vagrant'] actions = ['added', 'modified', 'removed'] # Testing the results for category in categories: for action in actions: + assert result[category][action] == [], logger.error(f'{result[category][action]} was found in: {category}{action}') def areAgent_processes_active(agent_params): @@ -304,7 +341,21 @@ def areAgent_processes_active(agent_params): Returns: str: Os name. """ - return bool([int(numero) for numero in Executor.execute_command(agent_params, 'pgrep wazuh').splitlines()]) + os_type = HostInformation.get_os_type(agent_params) + + if 'linux' in os_type: + result = ConnectionManager.execute_commands(agent_params, 'pgrep wazuh') + if result.get('success'): + return bool([int(numero) for numero in result.get('output').splitlines()]) + else: + return False + elif 'windows' in os_type: + result = ConnectionManager.execute_commands(agent_params, 'Get-Process -Name "wazuh-agent" | Format-Table -HideTableHeaders ProcessName') + if result.get('success'): + return 'wazuh-agent' in result.get('output') + else: + return False + def isAgent_port_open(agent_params): """ @@ -316,7 +367,15 @@ def isAgent_port_open(agent_params): Returns: str: Os name. """ - return 'ESTAB' in Executor.execute_command(agent_params, 'ss -t -a -n | grep ":1514" | grep ESTAB') + + os_type = HostInformation.get_os_type(agent_params) + if 'linux' in os_type: + result = ConnectionManager.execute_commands(agent_params, 'ss -t -a -n | grep ":1514" | grep ESTAB') + return result.get('success') + elif 'windows' in os_type : + result = ConnectionManager.execute_commands(agent_params, 'netstat -ano | Select-String -Pattern "TCP" | Select-String -Pattern "ESTABLISHED" | Select-String -Pattern ":1514"') + return 'ESTABLISHED' in result.get('output') + def get_agents_information(wazuh_api: WazuhAPI) -> list: """ @@ -337,17 +396,20 @@ def get_agents_information(wazuh_api: WazuhAPI) -> list: def get_agent_status(wazuh_api: WazuhAPI, agent_name) -> str: """ Function to get the status of an agent given its name. - + Args: - agents_data (list): List of dictionaries containing agents' data. - agent_name (str): Name of the agent whose status is to be obtained. - + Returns: - str: Status of the agent if found in the data, otherwise returns None. """ response = requests.get(f"{wazuh_api.api_url}/agents", headers=wazuh_api.headers, verify=False) + for agent in eval(response.text)['data']['affected_items']: if agent.get('name') == agent_name: + + return agent.get('status') return None diff --git a/deployability/modules/testing/tests/helpers/constants.py b/deployability/modules/testing/tests/helpers/constants.py index ea0c6a8098..b7f6619d8c 100755 --- a/deployability/modules/testing/tests/helpers/constants.py +++ b/deployability/modules/testing/tests/helpers/constants.py @@ -10,6 +10,13 @@ CONFIGURATIONS_DIR = Path(WAZUH_ROOT, "etc") WAZUH_CONF = Path(CONFIGURATIONS_DIR, "ossec.conf") CLIENT_KEYS = Path(CONFIGURATIONS_DIR, "client.keys") + +WINDOWS_ROOT_DIR = Path("C:", "Program Files (x86)", "ossec-agent") +WINDOWS_CONFIGURATIONS_DIR = Path(WINDOWS_ROOT_DIR, "etc") +WAZUH_WINDOWS_CONF = Path(WINDOWS_ROOT_DIR, "ossec.conf") +WINDOWS_CLIENT_KEYS = Path(WINDOWS_ROOT_DIR, "client.keys") +WINDOWS_VERSION = Path(WINDOWS_ROOT_DIR, "VERSION") +WINDOWS_REVISION = Path(WINDOWS_ROOT_DIR, "REVISION") # Binaries paths BINARIES_DIR = Path(WAZUH_ROOT, "bin") WAZUH_CONTROL = Path(BINARIES_DIR, "wazuh-control") diff --git a/deployability/modules/testing/tests/helpers/executor.py b/deployability/modules/testing/tests/helpers/executor.py index 6d204d6ba8..25f9ba191b 100644 --- a/deployability/modules/testing/tests/helpers/executor.py +++ b/deployability/modules/testing/tests/helpers/executor.py @@ -7,46 +7,106 @@ import subprocess import urllib3 import yaml +import winrm from base64 import b64encode - -class Executor: +class ConectionInventory(): + host: str + port: int + password: str | None = None + username: str + private_key_path: str | None = None @staticmethod - def execute_command(inventory_path, command) -> str: - + def _get_inventory_data(inventory_path) -> dict: 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') + return { + 'host': inventory_data.get('ansible_host'), + 'port': inventory_data.get('ansible_port'), + 'password': inventory_data.get('ansible_password', None), + 'username': inventory_data.get('ansible_user'), + 'private_key_path': inventory_data.get('ansible_ssh_private_key_file', None) + } + +class ConnectionManager: + @staticmethod + def _get_executor(inventory_path) -> type: + from .generic import HostInformation + + os_type = HostInformation.get_os_type(inventory_path) + if os_type == "windows": + return WindowsExecutor + else: + return UnixExecutor + + @staticmethod + def execute_commands(inventory_path, commands) -> dict: + executor = ConnectionManager._get_executor(inventory_path) + if isinstance(commands, str): + try: + result = executor._execute_command(ConectionInventory._get_inventory_data(inventory_path), commands) + except Exception as e: + raise Exception(f'Error executing command: {commands} with error: {e}') + return result + else: + results = {} + for command in commands: + result = executor._execute_command(ConectionInventory._get_inventory_data(inventory_path), command) + results[command] = result + return results + +class WindowsExecutor(): + @staticmethod + def _execute_command(data: ConectionInventory, command) -> dict: + if data.get('port') == 5986: + protocol = 'https' + else: + protocol = 'http' + + endpoint_url = f"{protocol}://{data.get('host')}:{data.get('port')}" + + try: + session = winrm.Session(endpoint_url, auth=(data.get('username'), data.get('password')),transport='ntlm', server_cert_validation='ignore') + ret = session.run_ps(command) + + if ret.status_code == 0: + return {'success': True, 'output': ret.std_out.decode('utf-8').strip()} + else: + return {'success': False, 'output': ret.std_err.decode('utf-8').strip()} + except Exception as e: + raise Exception(f'Error executing command: {command} with error: {e}') + +class UnixExecutor(): + @staticmethod + def _execute_command(data, command) -> dict: ssh_command = [ "ssh", - "-i", private_key_path, + "-i", data.get('private_key_path'), "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", - "-p", str(port), - f"{username}@{host}", - "sudo", + "-p", str(data.get('port')), + f"{data.get('username')}@{data.get('host')}", + "sudo", command ] - result = subprocess.run(ssh_command, stdout=subprocess.PIPE, text=True) - - return result.stdout + try: + ret = subprocess.run(ssh_command, stdout=subprocess.PIPE, text=True) + if ret.stdout: + return {'success': True, 'output': ret.stdout.replace('\n', '')} + if ret.stderr: + return {'success': False, 'output': ret.stderr.replace('\n', '')} + return {'success': False, 'output': None} - @staticmethod - def execute_commands(inventory_path, commands=[]) -> dict: - - results = {} - for command in commands: - results[command] = Executor.execute_command(inventory_path, command) + except Exception as e: + #return {'success': False, 'output': ret.stderr} + raise Exception(f'Error executing command: {command} with error: {e}') - return results +# ------------------------------------------------------ class WazuhAPI: @@ -62,14 +122,17 @@ def _authenticate(self): inventory_data = yaml.safe_load(yaml_file) 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","") + result = ConnectionManager.execute_commands(self.inventory_path, 'pwd') + file_path = result.get('output') + '/wazuh-install-files/wazuh-passwords.txt' + result = ConnectionManager.execute_commands(self.inventory_path, f'test -f {file_path} && echo "true" || echo "false"') + if not 'true' in result.get('output'): + ConnectionManager.execute_commands(self.inventory_path, 'tar -xvf wazuh-install-files.tar') + result = ConnectionManager.execute_commands(self.inventory_path, "grep api_password wazuh-install-files/wazuh-passwords.txt | head -n 1 | awk '{print $NF}'") + password = result.get('output')[1:-1] #-------------------------------------------------------------------------------- - + login_endpoint = 'security/user/authenticate' host = inventory_data.get('ansible_host') port = '55000' diff --git a/deployability/modules/testing/tests/helpers/generic.py b/deployability/modules/testing/tests/helpers/generic.py index 7c989028bf..65c1a13beb 100644 --- a/deployability/modules/testing/tests/helpers/generic.py +++ b/deployability/modules/testing/tests/helpers/generic.py @@ -12,9 +12,8 @@ import yaml from pathlib import Path -from .constants import WAZUH_CONTROL, CLIENT_KEYS -from .executor import Executor -from .utils import Utils +from .constants import WAZUH_CONTROL, CLIENT_KEYS, WINDOWS_CLIENT_KEYS, WINDOWS_VERSION, WINDOWS_REVISION +from .executor import Executor, ConnectionManager from modules.testing.utils import logger @@ -32,7 +31,14 @@ 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': + result = ConnectionManager.execute_commands(inventory_path, f'test -d {dir_path} && echo "True" || echo "False"') + + return result.get('output') + elif os_type == 'windows': + return ConnectionManager.execute_commands(inventory_path, f'Test-Path -Path "{dir_path}"').get('success') @staticmethod @@ -47,7 +53,12 @@ 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': + result = ConnectionManager.execute_commands(inventory_path, f'test -f {file_path} && echo "True" || echo "False"') + return result.get('output') + elif os_type == 'windows': + return ConnectionManager.execute_commands(inventory_path, f'Test-Path -Path "{file_path}"').get('output') @staticmethod @@ -61,8 +72,19 @@ 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() + try: + with open(inventory_path.replace('inventory', 'track'), 'r') as file: + data = yaml.safe_load(file) + if 'platform' in data: + return data['platform'] + else: + raise KeyError("The 'platform' key was not found in the YAML file.") + except FileNotFoundError: + logger.error(f"The YAML file '{inventory_path}' was not found.") + except yaml.YAMLError as e: + logger.error(f"Error while loading the YAML file: {e}") + except Exception as e: + logger.error(f"An unexpected error occurred: {e}") @staticmethod @@ -74,9 +96,21 @@ def get_architecture(inventory_path) -> str: inventory_path: host's inventory path Returns: - str: architecture (aarch64, x86_64, intel, apple) + str: architecture (amd64, arm64, intel, apple) """ - return Executor.execute_command(inventory_path, 'uname -m') + try: + with open(inventory_path.replace('inventory', 'track'), 'r') as file: + data = yaml.safe_load(file) + if 'platform' in data: + return data['arch'] + else: + raise KeyError("The 'platform' key was not found in the YAML file.") + except FileNotFoundError: + logger.error(f"The YAML file '{inventory_path}' was not found.") + except yaml.YAMLError as e: + logger.error(f"Error while loading the YAML file: {e}") + except Exception as e: + logger.error(f"An unexpected error occurred: {e}") @staticmethod @@ -115,9 +149,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 +167,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) @@ -145,13 +179,22 @@ def get_os_name_and_version_from_inventory(inventory_path) -> tuple: @staticmethod def get_os_version_from_inventory(inventory_path) -> str: - if 'manager' in inventory_path: - os_version = re.search(r".*?/manager-linux-.*?-(.*?)-.*?/inventory.yaml", inventory_path).group(1) - elif 'agent' in inventory_path: - os_version = re.search(r".*?/agent-linux-.*?-(.*?)-.*?/inventory.yaml", inventory_path).group(1) - return os_version - else: - return None + os_type = HostInformation.get_os_type(inventory_path) + + if os_type == 'linux': + if 'manager' in inventory_path: + os_version = re.search(r".*?/manager-.*?-.*?-(.*?)-.*?/inventory.yaml", inventory_path).group(1) + elif 'agent' in inventory_path: + os_version = re.search(r".*?/agent-.*?-.*?-(.*?)-.*?/inventory.yaml", inventory_path).group(1) + return os_version + else: + return None + elif os_type == 'windows': + if 'agent' in inventory_path: + os_version = re.search(r".*?/agent-.*?-.*?-(.*?)-.*?/inventory.yaml", inventory_path)[1:3] + return os_version + else: + return None @staticmethod def get_current_dir(inventory_path) -> str: @@ -164,8 +207,13 @@ def get_current_dir(inventory_path) -> str: Returns: str: current directory """ + os_type = HostInformation.get_os_type(inventory_path) - return Executor.execute_command(inventory_path, 'pwd').replace("\n","") + if os_type == 'linux': + result = ConnectionManager.execute_commands(inventory_path, 'pwd') + return result.get('output').replace("\n","") + elif os_type == 'windows': + return ConnectionManager.execute_commands(inventory_path, '(Get-Location).Path').get('output') @staticmethod def get_internal_ip_from_aws_dns(dns_name): @@ -198,7 +246,14 @@ 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 = ConnectionManager.execute_commands(inventory_path, f'cat {CLIENT_KEYS}').get('output') + elif os_type == 'windows': + client_key = ConnectionManager.execute_commands(inventory_path, f'Get-Content "{WINDOWS_CLIENT_KEYS}"').get('output') + lines = client_key.split('\n')[:-1] for line in lines: _id, name, address, password = line.strip().split() @@ -225,7 +280,7 @@ def sshd_config(inventory_path) -> None: """ 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) + ConnectionManager.execute_commands(inventory_path, commands) @staticmethod @@ -237,17 +292,25 @@ 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) + 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)}') + 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'): + ConnectionManager.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 == 'windows': + commands = ["Set-NetFirewallProfile -Profile Domain,Public,Private -Enabled False"] + ConnectionManager.execute_commands(inventory_path, commands) def _extract_hosts(paths, is_aws): + from .utils import Utils if is_aws: return [HostInformation.get_internal_ip_from_aws_dns(Utils.extract_ansible_host(path)) for path in paths] else: @@ -266,6 +329,8 @@ def certs_create(wazuh_version, master_path, dashboard_path, indexer_paths=[], w workers_paths (list): wazuh worker paths list """ + from .utils import Utils + current_directory = HostInformation.get_current_dir(master_path) wazuh_version = '.'.join(wazuh_version.split('.')[:2]) @@ -326,7 +391,7 @@ def certs_create(wazuh_version, master_path, dashboard_path, indexer_paths=[], w commands.extend(certs_creation) - Executor.execute_commands(master_path, commands) + ConnectionManager.execute_commands(master_path, commands) current_from_directory = HostInformation.get_current_dir(master_path) @@ -364,7 +429,7 @@ def scp_to(from_inventory_path, to_inventory_path, file_name) -> None: # Allowing handling permissions if file_name == 'wazuh-install-files.tar': - Executor.execute_command(from_inventory_path, f'chmod +rw {file_name}') + ConnectionManager.execute_commands(from_inventory_path, f'chmod +rw {file_name}') logger.info('File permissions modified to be handled') # SCP @@ -381,8 +446,8 @@ def scp_to(from_inventory_path, to_inventory_path, file_name) -> None: # 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}') + ConnectionManager.execute_commands(from_inventory_path, f'chmod 600 {file_name}') + ConnectionManager.execute_commands(to_inventory_path, f'chmod 600 {file_name}') logger.info('File permissions were restablished') # Deleting file from localhost @@ -461,18 +526,52 @@ def file_monitor(monitored_file: str, target_string: str, timeout: int = 30) -> class CheckFiles: @staticmethod - def _checkfiles(inventory_path, os_type, directory, filter= None, hash_algorithm='sha256') -> dict: + def _checkfiles(inventory_path, os_type, directory, filters_keywords= None, hash_algorithm='sha256') -> dict: """ It captures a structure of a directory Returns: Dict: dict of directories:hash """ if 'linux' in os_type or 'macos' in os_type: - command = f'sudo find {directory} -type f -exec sha256sum {{}} + {filter}' - result = Executor.execute_command(inventory_path, command) + filters = f"| grep -v {filters_keywords[0]}" + for filter_ in filters_keywords[1:]: + filters += f" | grep -v {filter_}" + command = f'sudo find {directory} -type f -exec sha256sum {{}} + {filters}' + result = ConnectionManager.execute_commands(inventory_path, command).get('output') elif 'windows' in os_type: - command = 'dir /a-d /b /s | findstr /v /c:"\\.$" /c:"\\..$"| find /c ":"' + + quoted_filters = ['"{}"'.format(keyword) for keyword in filters_keywords] + filter_files = ",".join(quoted_filters) + command = f"$includedDirectories = @('{directory}') " + command += f"\n$excludedPatterns = @({filter_files})" + command += """ + try { + foreach ($dir in $includedDirectories) { + Get-ChildItem -Path "$dir" -Recurse -File -ErrorAction SilentlyContinue | ForEach-Object { + $fileName = $_.FullName + $hash = Get-FileHash -Path $fileName -Algorithm SHA256 -ErrorAction SilentlyContinue + if ($hash) { + $exclude = $false + foreach ($pattern in $excludedPatterns) { + if ($fileName -like "*$pattern*") { + $exclude = $true + break + } + } + if (-not $exclude) { + Write-Output "$($hash.Hash) $fileName" + } + } + } + } + } catch { + Write-Host "Error: $_" + } + + """ + + result = ConnectionManager.execute_commands(inventory_path, command).get('output') else: logger.info(f'Unsupported operating system') return None @@ -485,9 +584,9 @@ def _checkfiles(inventory_path, os_type, directory, filter= None, hash_algorithm @staticmethod - def _perform_scan(inventory_path, os_type, directories, filters): + def _perform_scan(inventory_path, os_type, directories, filters_keywords): logger.info(f'Generating Snapshot for Checkfile in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - return {directory: CheckFiles._checkfiles(inventory_path, os_type, directory, filters) for directory in directories} + return {directory: CheckFiles._checkfiles(inventory_path, os_type, directory, filters_keywords) for directory in directories} @staticmethod @@ -514,16 +613,16 @@ 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 os_type == 'linux': + 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'] + elif os_type == 'windows': + directories = ['C:\\Program Files', 'C:\\Program Files (x86)','C:\\Users\\vagrant'] + filters_keywords = ['log','tmp','ossec-agent', 'EdgeUpdate'] - for filter_ in filters_keywords[1:]: - filters+= f" | grep -v {filter_}" - - initial_scans = CheckFiles._perform_scan(inventory_path, os_type, directories, filters) + initial_scans = CheckFiles._perform_scan(inventory_path, os_type, directories, filters_keywords) callback() - second_scans = CheckFiles._perform_scan(inventory_path, os_type, directories, filters) + second_scans = CheckFiles._perform_scan(inventory_path, os_type, directories, filters_keywords) changes = {directory: CheckFiles._calculate_changes(initial_scans[directory], second_scans[directory]) for directory in directories} return changes @@ -543,8 +642,14 @@ 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)}') + os_type = HostInformation.get_os_type(inventory_path) - return Executor.execute_command(inventory_path, f'systemctl status {host_role}') + if os_type == 'linux': + return ConnectionManager.execute_commands(inventory_path, f'systemctl status {host_role}').get('output') + elif os_type == 'windows': + result = ConnectionManager.execute_commands(inventory_path, "Get-Service -Name 'Wazuh' | Format-Table -HideTableHeaders Status") + if result.get('success'): + return result.get('output') @staticmethod @@ -558,7 +663,12 @@ def component_stop(inventory_path, host_role) -> None: """ 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': + ConnectionManager.execute_commands(inventory_path, f'systemctl stop {host_role}') + elif os_type == 'windows': + ConnectionManager.execute_commands(inventory_path, f'NET STOP Wazuh') @staticmethod @@ -572,7 +682,13 @@ 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': + ConnectionManager.execute_commands(inventory_path, f'systemctl restart {host_role}') + elif os_type == 'windows': + ConnectionManager.execute_commands(inventory_path, 'NET STOP Wazuh') + ConnectionManager.execute_commands(inventory_path, 'NET START Wazuh') @staticmethod @@ -586,7 +702,13 @@ 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': + ConnectionManager.execute_commands(inventory_path, f'systemctl start {host_role}') + elif os_type == 'windows': + ConnectionManager.execute_commands(inventory_path, 'NET START Wazuh') @staticmethod @@ -600,7 +722,12 @@ 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 ConnectionManager.execute_commands(inventory_path, f'{WAZUH_CONTROL} info -v').get('output') + elif os_type == 'windows': + return ConnectionManager.execute_commands(inventory_path, f'Get-Content "{WINDOWS_VERSION}"').get('output')#.replace("\n", "")) @staticmethod @@ -614,7 +741,13 @@ 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 ConnectionManager.execute_commands(inventory_path, f'{WAZUH_CONTROL} info -r').get('output') + elif os_type == 'windows': + return ConnectionManager.execute_commands(inventory_path, f'Get-Content "{WINDOWS_REVISION}"').get('output') @staticmethod @@ -628,8 +761,17 @@ 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': + result = ConnectionManager.execute_commands(inventory_path, f'[ -f {CLIENT_KEYS} ] && echo true || echo false') + return 'true' in result.get('output') + elif os_type == 'windows': + result = ConnectionManager.execute_commands(inventory_path, f'Test-Path -Path "{WINDOWS_CLIENT_KEYS}"') + if result.get('success'): + return result.get('output', '') + return False @staticmethod def isComponentActive(inventory_path, host_role) -> bool: @@ -643,7 +785,15 @@ 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' == ConnectionManager.execute_commands(inventory_path, f'systemctl is-active {host_role}').get('output').replace("\n", "") + elif os_type == 'windows': + result = ConnectionManager.execute_commands(inventory_path, "Get-Service -Name 'Wazuh'") + return result.get('success') class Waits: diff --git a/deployability/modules/testing/tests/helpers/manager.py b/deployability/modules/testing/tests/helpers/manager.py index 2ac8811ab7..b1c38ff251 100644 --- a/deployability/modules/testing/tests/helpers/manager.py +++ b/deployability/modules/testing/tests/helpers/manager.py @@ -6,7 +6,7 @@ import socket from .constants import CLUSTER_CONTROL, AGENT_CONTROL, WAZUH_CONF, WAZUH_ROOT -from .executor import Executor, WazuhAPI +from .executor import Executor, WazuhAPI, ConnectionManager from .generic import HostInformation, CheckFiles from modules.testing.utils import logger from .utils import Utils @@ -39,7 +39,7 @@ def install_manager(inventory_path, node_name, wazuh_version) -> None: f"bash wazuh-install.sh --wazuh-server {node_name} --ignore-check" ] logger.info(f'Installing Manager in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - Executor.execute_commands(inventory_path, commands) + ConnectionManager.execute_commands(inventory_path, commands) @staticmethod @@ -88,7 +88,7 @@ def uninstall_manager(inventory_path) -> None: commands.extend(system_commands) logger.info(f'Uninstalling Manager in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') - Executor.execute_commands(inventory_path, commands) + ConnectionManager.execute_commands(inventory_path, commands) @staticmethod @@ -188,7 +188,7 @@ def perform_install_and_scan_for_manager(manager_params, manager_name, wazuh_par action_callback = lambda: WazuhManager._install_manager_callback(wazuh_params, manager_name, manager_params) result = WazuhManager.perform_action_and_scan(manager_params, action_callback) logger.info(f'Pre and post install checkfile comparison in {HostInformation.get_os_name_and_version_from_inventory(manager_params)}: {result}') - WazuhManager.assert_results(result) + WazuhManager.assert_results(result, manager_params) @staticmethod @@ -204,7 +204,7 @@ def perform_uninstall_and_scan_for_manager(manager_params) -> None: action_callback = lambda: WazuhManager._uninstall_manager_callback(manager_params) result = WazuhManager.perform_action_and_scan(manager_params, action_callback) logger.info(f'Pre and post uninstall checkfile comparison in {HostInformation.get_os_name_and_version_from_inventory(manager_params)}: {result}') - WazuhManager.assert_results(result) + WazuhManager.assert_results(result, manager_params) @staticmethod @@ -236,7 +236,7 @@ def get_cluster_info(inventory_path) -> None: str: Cluster status """ - return Executor.execute_command(inventory_path, f'{CLUSTER_CONTROL} -l') + return ConnectionManager.execute_commands(inventory_path, f'{CLUSTER_CONTROL} -l').get('output') @staticmethod @@ -250,8 +250,10 @@ def get_agent_control_info(inventory_path) -> None: Returns: str: Agents status """ + result = ConnectionManager.execute_commands(inventory_path, f'{AGENT_CONTROL} -l') - return Executor.execute_command(inventory_path, f'{AGENT_CONTROL} -l') + + return result.get('output') @staticmethod @@ -278,8 +280,8 @@ def configuring_clusters(inventory_path, node_name, node_type, node_to_connect_i "systemctl restart wazuh-manager" ] - Executor.execute_commands(inventory_path, commands) - if node_name in Executor.execute_command(inventory_path, f'cat {WAZUH_CONF}'): + ConnectionManager.execute_commands(inventory_path, commands) + if node_name in ConnectionManager.execute_commands(inventory_path, f'cat {WAZUH_CONF}').get('output'): logger.info(f'Cluster configured in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') else: logger.error(f'Error configuring cluster information in: {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}') diff --git a/deployability/modules/testing/tests/helpers/utils.py b/deployability/modules/testing/tests/helpers/utils.py index aea456c151..f57ea7b77c 100644 --- a/deployability/modules/testing/tests/helpers/utils.py +++ b/deployability/modules/testing/tests/helpers/utils.py @@ -7,16 +7,17 @@ import yaml import logging import time +import winrm from modules.testing.utils import logger - +from .generic import HostInformation paramiko_logger = logging.getLogger("paramiko") paramiko_logger.setLevel(logging.CRITICAL) class Utils: - + @staticmethod def extract_ansible_host(file_path) -> str: with open(file_path, 'r') as yaml_file: @@ -26,9 +27,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}') @@ -39,31 +40,56 @@ def check_inventory_connection(inventory_path, attempts=10, sleep=30) -> bool: raise FileNotFoundError(logger.error(f'File not found in {os_name}')) 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') + private_key_path = inventory_data.get('ansible_ssh_private_key_file', None) username = inventory_data.get('ansible_user') + password = inventory_data.get('ansible_password', None) - ssh = paramiko.SSHClient() - ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) - private_key = paramiko.RSAKey.from_private_key_file(private_key_path) - for attempt in range(1, attempts + 1): + os_type = HostInformation.get_os_type(inventory_path) + + if os_type == 'linux': 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) + elif os_type == 'windows': + if port == 5986: + protocol = 'https' + else: + protocol = 'http' + endpoint_url = f'{protocol}://{host}:{port}' + + for attempt in range(1, attempts + 1): + try: + session = winrm.Session(endpoint_url, auth=(username, password),transport='ntlm', server_cert_validation='ignore') + cmd = session.run_cmd('ipconfig') + if cmd.status_code == 0: + logger.info("WinRM connection successful.") + return True + else: + logger.error(f'WinRM connection failed. Check the credentials in the inventory file.') + 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_basic_info.py b/deployability/modules/testing/tests/test_agent/test_basic_info.py index 3b81a0765f..5066ee6f4e 100644 --- a/deployability/modules/testing/tests/test_agent/test_basic_info.py +++ b/deployability/modules/testing/tests/test_agent/test_basic_info.py @@ -6,7 +6,7 @@ import re from ..helpers.agent import WazuhAgent, WazuhAPI -from ..helpers.constants import WAZUH_ROOT +from ..helpers.constants import WAZUH_ROOT, WINDOWS_ROOT_DIR from ..helpers.generic import HostConfiguration, HostInformation, GeneralComponentActions, Waits from modules.testing.utils import logger from ..helpers.manager import WazuhManager @@ -67,10 +67,22 @@ def setup_test_environment(wazuh_params): def test_wazuh_os_version(wazuh_params): wazuh_api = WazuhAPI(wazuh_params['master']) for agent_names, agent_params in wazuh_params['agents'].items(): - 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)}') + path_to_check = '' + os_type = HostInformation.get_os_type(agent_params) + + if os_type == 'linux': + path_to_check = WAZUH_ROOT + elif os_type == 'windows': + path_to_check = WINDOWS_ROOT_DIR + + assert HostInformation.dir_exists(agent_params, path_to_check), logger.error(f'The {path_to_check} is not present in {HostInformation.get_os_name_and_version_from_inventory(agent_params)}') + expected_condition_func = lambda: 'active' == WazuhAgent.get_agent_status(wazuh_api, agent_names) Waits.dynamic_wait(expected_condition_func, cycles=20, waiting_time=30) - assert HostInformation.get_os_version_from_inventory(agent_params) in WazuhAgent.get_agent_os_version_by_name(wazuh_api, agent_names), logger.error('There is a mismatch between the OS version and the OS version of the installed agent') + + if not os_type == 'windows': + assert HostInformation.get_os_version_from_inventory(agent_params) in WazuhAgent.get_agent_os_version_by_name(wazuh_api, agent_names), logger.error('There is a mismatch between the OS version and the OS version of the installed agent') + assert HostInformation.get_os_name_from_inventory(agent_params) in WazuhAgent.get_agent_os_name_by_name(wazuh_api, agent_names).replace(' ', ''), logger.error('There is a mismatch between the OS name and the OS name of the installed agent') diff --git a/deployability/modules/testing/tests/test_agent/test_connection.py b/deployability/modules/testing/tests/test_agent/test_connection.py index 4a7297596a..ec720925ce 100644 --- a/deployability/modules/testing/tests/test_agent/test_connection.py +++ b/deployability/modules/testing/tests/test_agent/test_connection.py @@ -73,7 +73,8 @@ def test_connection(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') + status = GeneralComponentActions.get_component_status(agent, 'wazuh-agent') + assert 'active' in status or 'connected' in status or "Running" in status, logger.error(f'The {HostInformation.get_os_name_and_version_from_inventory(agent)} is not active') def test_service(wazuh_params): diff --git a/deployability/modules/testing/tests/test_agent/test_install.py b/deployability/modules/testing/tests/test_agent/test_install.py index 059d46273f..eea9bef899 100644 --- a/deployability/modules/testing/tests/test_agent/test_install.py +++ b/deployability/modules/testing/tests/test_agent/test_install.py @@ -6,7 +6,7 @@ import re from ..helpers.agent import WazuhAgent -from ..helpers.constants import WAZUH_ROOT +from ..helpers.constants import WAZUH_ROOT, WINDOWS_ROOT_DIR from ..helpers.generic import HostConfiguration, HostInformation, GeneralComponentActions from modules.testing.utils import logger from ..helpers.manager import WazuhManager @@ -81,15 +81,22 @@ def test_installation(wazuh_params): assert HostInformation.dir_exists(wazuh_params['master'], WAZUH_ROOT), logger.error(f'The {WAZUH_ROOT} is not present in {HostInformation.get_os_name_and_version_from_inventory(wazuh_params["master"])}') # Agent installation - for agent_names, agent_params in wazuh_params['agents'].items(): - WazuhAgent.perform_install_and_scan_for_agent(agent_params, agent_names, wazuh_params) + for agent_name, agent_params in wazuh_params['agents'].items(): + WazuhAgent.perform_install_and_scan_for_agent(agent_params, agent_name, wazuh_params) + #WazuhAgent.install_agent(agent_params, agent_name, wazuh_params['wazuh_version'], wazuh_params['wazuh_revision'], wazuh_params['live']) # 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)}') + os_type = HostInformation.get_os_type(agent) + if os_type == 'linux': + path_to_check = WAZUH_ROOT + elif os_type == 'windows': + path_to_check = WINDOWS_ROOT_DIR + + assert HostInformation.dir_exists(agent, path_to_check), logger.error(f'The {path_to_check} is not present in {HostInformation.get_os_name_and_version_from_inventory(agent)}') 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 'Stopped' in agent_status, logger.error(f'The {HostInformation.get_os_name_and_version_from_inventory(agent)} status is not loaded') diff --git a/deployability/modules/testing/tests/test_agent/test_registration.py b/deployability/modules/testing/tests/test_agent/test_registration.py index dc756410d5..7b1461f7f8 100644 --- a/deployability/modules/testing/tests/test_agent/test_registration.py +++ b/deployability/modules/testing/tests/test_agent/test_registration.py @@ -68,14 +68,8 @@ def test_status(wazuh_params): for agent_names, agent_params in wazuh_params['agents'].items(): WazuhAgent.register_agent(agent_params, wazuh_params['master']) 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') - - -def test_connection(wazuh_params): - for agent_names, agent_params in wazuh_params['agents'].items(): - assert agent_names in WazuhManager.get_agent_control_info(wazuh_params['master']), f'The {agent_names} is not present in the master by command' - wazuh_api = WazuhAPI(wazuh_params['master']) - assert any(d.get('name') == agent_names for d in WazuhAgent.get_agents_information(wazuh_api)), logger.error(f'The {agent_names} is not present in the master by API') + status = GeneralComponentActions.get_component_status(agent, 'wazuh-agent') + assert 'active' in status or 'Running' in status, logger.error(f'The {HostInformation.get_os_name_and_version_from_inventory(agent)} is not active') def test_service(wazuh_params): @@ -86,6 +80,12 @@ def test_service(wazuh_params): expected_condition_func = lambda: 'active' == WazuhAgent.get_agent_status(wazuh_api, agent_names) Waits.dynamic_wait(expected_condition_func, cycles=20, waiting_time=30) +def test_connection(wazuh_params): + for agent_names, agent_params in wazuh_params['agents'].items(): + wazuh_api = WazuhAPI(wazuh_params['master']) + assert agent_names in WazuhManager.get_agent_control_info(wazuh_params['master']), f'The {agent_names} is not present in the master by command' + assert any(d.get('name') == agent_names for d in WazuhAgent.get_agents_information(wazuh_api)), logger.error(f'The {agent_names} is not present in the master by API') + def test_clientKeys(wazuh_params): for agent_names, agent_params in wazuh_params['agents'].items(): diff --git a/deployability/modules/testing/tests/test_agent/test_restart.py b/deployability/modules/testing/tests/test_agent/test_restart.py index 734e4791e3..9a4abb9866 100644 --- a/deployability/modules/testing/tests/test_agent/test_restart.py +++ b/deployability/modules/testing/tests/test_agent/test_restart.py @@ -69,8 +69,8 @@ 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') - + status = GeneralComponentActions.get_component_status(agent_params, 'wazuh-agent') + assert 'active' in status or 'Running' in status, logger.error(f'{agent_names} is not active by command') def test_connection(wazuh_params): for agent_names, agent_params in wazuh_params['agents'].items(): diff --git a/deployability/modules/testing/tests/test_agent/test_stop.py b/deployability/modules/testing/tests/test_agent/test_stop.py index a347bce95f..b673726002 100644 --- a/deployability/modules/testing/tests/test_agent/test_stop.py +++ b/deployability/modules/testing/tests/test_agent/test_stop.py @@ -71,8 +71,8 @@ def test_service(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') + status = GeneralComponentActions.get_component_status(agent_params, 'wazuh-agent') + assert 'inactive' in status or 'Stopped' in status or 'StopPending' in status or 'not running' in status, 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 953e73bc5e..c66e67cef1 100644 --- a/deployability/modules/testing/tests/test_agent/test_uninstall.py +++ b/deployability/modules/testing/tests/test_agent/test_uninstall.py @@ -6,7 +6,7 @@ import re from ..helpers.agent import WazuhAgent -from ..helpers.constants import WAZUH_ROOT +from ..helpers.constants import WAZUH_ROOT, WINDOWS_CONFIGURATIONS_DIR, WINDOWS_ROOT_DIR from ..helpers.generic import HostInformation, GeneralComponentActions, Waits from ..helpers.manager import WazuhManager, WazuhAPI from modules.testing.utils import logger @@ -66,11 +66,19 @@ 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}') + + os_type = HostInformation.get_os_type(agent_params) + if os_type == 'linux': + path_to_check = WAZUH_ROOT + elif os_type == 'windows': + path_to_check = WINDOWS_ROOT_DIR + + assert HostInformation.dir_exists(agent_params, path_to_check), logger.error(f'The {path_to_check} is not present in the host {agent_names}') # Agent installation for agent_names, agent_params in wazuh_params['agents'].items(): WazuhAgent.perform_uninstall_and_scan_for_agent(agent_params,wazuh_params) + #WazuhAgent.uninstall_agent(agent_params, wazuh_params['wazuh_version'], wazuh_params['wazuh_revision']) # Manager uninstallation status check for agent_names, agent_params in wazuh_params['agents'].items(): @@ -79,7 +87,12 @@ def test_uninstall(wazuh_params): def test_agent_uninstalled_directory(wazuh_params): for agent_names, agent_params in wazuh_params['agents'].items(): - assert not HostInformation.dir_exists(agent_params, WAZUH_ROOT), logger.error(f'The {WAZUH_ROOT} is still present in the agent {agent_names}') + os_type = HostInformation.get_os_type(agent_params) + if os_type == 'linux': + path_to_check = WAZUH_ROOT + elif os_type == 'windows': + path_to_check = WINDOWS_CONFIGURATIONS_DIR + assert HostInformation.dir_exists(agent_params, path_to_check), logger.error(f'The {path_to_check} is still present in the agent {agent_names}') def test_service(wazuh_params): diff --git a/deployability/modules/workflow_engine/examples/agent/aws/test-agent-windows-complete.yaml b/deployability/modules/workflow_engine/examples/agent/aws/test-agent-windows-complete.yaml new file mode 100755 index 0000000000..c65d56e65e --- /dev/null +++ b/deployability/modules/workflow_engine/examples/agent/aws/test-agent-windows-complete.yaml @@ -0,0 +1,119 @@ +version: 0.1 +description: This workflow is used to test agents' deployment for DDT1 PoC +variables: + agent-os: + - linux-ubuntu-20.04-amd64 + - linux-debian-12-amd64 + - linux-oracle-9-amd64 + - linux-centos-8-amd64 + - linux-redhat-9-amd64 + - windows-desktop-10-amd64 + - windows-server-2012r2-amd64 + - windows-server-2016-amd64 + - windows-server-2019-amd64 + - windows-server-2022-amd64 + manager-os: linux-ubuntu-22.04-amd64 + infra-provider: aws + working-dir: /tmp/dtt1-poc + +tasks: + # Unique manager allocate task + - task: "allocate-manager-{manager-os}" + description: "Allocate resources for the manager." + do: + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: create + - provider: "{infra-provider}" + - size: large + - composite-name: "{manager-os}" + - inventory-output: "{working-dir}/manager-{manager-os}/inventory.yaml" + - track-output: "{working-dir}/manager-{manager-os}/track.yaml" + - label-termination-date: "1d" + - label-team: "qa" + on-error: "abort-all" + cleanup: + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: delete + - track-output: "{working-dir}/manager-{manager-os}/track.yaml" + + # Unique agent allocate task + - task: "allocate-agent-{agent}" + description: "Allocate resources for the agent." + do: + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: create + - provider: "{infra-provider}" + - size: medium + - composite-name: "{agent}" + - inventory-output: "{working-dir}/agent-{agent}/inventory.yaml" + - track-output: "{working-dir}/agent-{agent}/track.yaml" + - label-termination-date: "1d" + - label-team: "qa" + on-error: "abort-all" + foreach: + - variable: agent-os + as: agent + cleanup: + this: process + with: + path: python3 + args: + - modules/allocation/main.py + - action: delete + - track-output: "{working-dir}/agent-{agent}/track.yaml" + depends-on: + - "provision-manager-{manager-os}" + + # Unique manager provision task + - task: "provision-manager-{manager-os}" + description: "Provision the manager." + do: + this: process + with: + path: python3 + args: + - modules/provision/main.py + - inventory: "{working-dir}/manager-{manager-os}/inventory.yaml" + - install: + - component: wazuh-manager + type: assistant + version: 4.7.3 + live: True + depends-on: + - "allocate-manager-{manager-os}" + on-error: "abort-all" + + # Generic agent test task + - task: "run-agent-{agent}-tests" + description: "Run tests install for the agent {agent}." + do: + this: process + with: + path: python3 + args: + - modules/testing/main.py + - targets: + - wazuh-1: "{working-dir}/manager-{manager-os}/inventory.yaml" + - agent: "{working-dir}/agent-{agent}/inventory.yaml" + - tests: "install,registration,connection,basic_info,restart,stop,uninstall" + - component: "agent" + - wazuh-version: "4.7.3" + - wazuh-revision: "40714" + - live: "True" + foreach: + - variable: agent-os + as: agent + depends-on: + - "allocate-agent-{agent}" diff --git a/deployability/modules/workflow_engine/examples/agent/aws/test-agent-windows-install.yaml b/deployability/modules/workflow_engine/examples/agent/aws/test-agent-windows-install.yaml new file mode 100755 index 0000000000..1c8a8f1faf --- /dev/null +++ b/deployability/modules/workflow_engine/examples/agent/aws/test-agent-windows-install.yaml @@ -0,0 +1,28 @@ +version: 0.1 +description: This workflow is used to test agents' deployment for DDT1 PoC +variables: + agent-os: + - windows-server-2016-amd64 + manager-os: linux-ubuntu-22.04-amd64 + infra-provider: vagrant + working-dir: /tmp/dtt1-poc + +tasks: + # Generic agent test task + - task: "run-agent-windows-server-2016-amd64-tests" + description: "Run tests install for the agent windows-server-2016-amd64." + do: + this: process + with: + path: python3 + args: + - modules/testing/main.py + - targets: + - wazuh-1: "{working-dir}/manager-{manager-os}/inventory.yaml" + - agent: "{working-dir}/agent-windows-server-2016-amd64/inventory.yaml" + - tests: "install" + - component: "agent" + - wazuh-version: "4.7.3" + - wazuh-revision: "40714" + - live: "True" +