Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Central components (AIO Installation) Windows and MacOS tests integration #5324

Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deployability/modules/testing/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def parse_arguments():
parser = argparse.ArgumentParser(description="Wazuh testing tool")
parser.add_argument("--targets", action='append', default=[], required=True)
parser.add_argument("--tests", required=True)
parser.add_argument("--component", choices=['manager', 'agent'], required=True)
parser.add_argument("--component", choices=['manager', 'agent', 'central_components'], required=True)
parser.add_argument("--dependencies", action='append', default=[], required=False)
parser.add_argument("--cleanup", required=False, default=True)
parser.add_argument("--wazuh-version", required=True)
Expand Down
2 changes: 1 addition & 1 deletion deployability/modules/testing/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

class ExtraVars(BaseModel):
"""Extra vars for testing module."""
component: Literal['manager', 'agent']
component: Literal['manager', 'agent', 'central_components']
wazuh_version: str
wazuh_revision: str
wazuh_branch: str | None = None
Expand Down
3 changes: 3 additions & 0 deletions deployability/modules/testing/tests/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@
from .generic import HostConfiguration, HostInformation, HostMonitor, CheckFiles
from .agent import WazuhAgent
from .manager import WazuhManager
from .indexer import WazuhIndexer
from .dashboard import WazuhDashboard
from .central import WazuhCentralComponents
21 changes: 0 additions & 21 deletions deployability/modules/testing/tests/helpers/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,6 @@ def perform_uninstall_and_scan_for_agent(agent_params, wazuh_params) -> None:

@staticmethod
def assert_results(result, params = None) -> None:

"""
Gets the status of an agent given its name.

Expand Down Expand Up @@ -511,26 +510,6 @@ def get_agent_ip_status_and_name_by_id(wazuh_api: WazuhAPI, identifier):
return [None, None, None]


def get_agent_ip_status_and_name_by_id(wazuh_api: WazuhAPI, identifier):
"""
Get IP status and name by ID.

Args:
identifier (str): Agent ID.

Returns:
List: IP, name, and status of the agent.
"""
try:
agents_information = wazuh_api.get_agents_information()
for element in agents_information:
if element['id'] == identifier:
return [element['ip'], element['name'], element['status']]
except Exception as e:
logger.error(f"Unexpected error: {e}")
return [None, None, None]


def get_agent_os_version_by_name(wazuh_api: WazuhAPI, agent_name):
"""
Get Agent os version by Agent name
Expand Down
172 changes: 172 additions & 0 deletions deployability/modules/testing/tests/helpers/central.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
# Copyright (C) 2015, Wazuh Inc.
# Created by Wazuh, Inc. <[email protected]>.
# This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2

from .executor import ConnectionManager
from .generic import HostInformation, CheckFiles
from modules.testing.utils import logger


class WazuhCentralComponents:

@staticmethod
def install_aio(inventory_path, wazuh_version) -> None:
"""
Installs Wazuh central components (AIO) in the host

Args:
inventory_path (str): host's inventory path
wazuh_version (str): major.minor.patch

"""
wazuh_version = '.'.join(wazuh_version.split('.')[:2])
os_name = HostInformation.get_os_name_from_inventory(inventory_path)

if HostInformation.has_curl(inventory_path):
commands = [
f"curl -sO https://packages.wazuh.com/{wazuh_version}/wazuh-install.sh && sudo bash ./wazuh-install.sh -a --ignore-check"
]
else:
commands = [
f"wget https://packages.wazuh.com/{wazuh_version}/wazuh-install.sh && sudo bash ./wazuh-install.sh -a --ignore-check"
]


logger.info(f'Installing Wazuh central components (AIO) in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}')
ConnectionManager.execute_commands(inventory_path, commands)


@staticmethod
def uninstall_aio(inventory_path) -> None:
"""
Uninstall Wazuh Central Components (AIO) in the host

Args:
inventory_paths (str): hosts' inventory path
"""

commands = ['bash wazuh-install.sh --uninstall --ignore-check']

logger.info(f'Uninstalling Wazuh central components (AIO) in {HostInformation.get_os_name_and_version_from_inventory(inventory_path)}')
ConnectionManager.execute_commands(inventory_path, commands)


@staticmethod
def _install_aio_callback(wazuh_params, host_params):
WazuhCentralComponents.install_aio(host_params, wazuh_params['wazuh_version'])


@staticmethod
def _uninstall_aio_callback(host_params):
WazuhCentralComponents.uninstall_aio(host_params)


@staticmethod
def perform_action_and_scan(host_params, action_callback) -> dict:
"""
Takes scans using filters, the callback action and compares the result

Args:
host_params (str): host parameters
callback (cb): callback (action)

Returns:
result (dict): comparison brief

"""
result = CheckFiles.perform_action_and_scan(host_params, action_callback)
os_name = HostInformation.get_os_name_from_inventory(host_params)
logger.info(f'Applying filters in checkfiles in {HostInformation.get_os_name_and_version_from_inventory(host_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': ['filebeat'],
'modified': []
},
'/root': {'added': ['trustdb.gpg', 'lesshst', 'ssh'], 'removed': ['filebeat'], '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': ['filebeat'], 'modified': []},
'/root': {'added': ['trustdb.gpg', 'lesshst'], 'removed': [], 'modified': ['.rnd']},
'/usr/sbin': {'added': [], 'removed': [], 'modified': []}
}

# Use of filters
for directory, changes in result.items():
if directory in filter_data:
for change, files in changes.items():
if change in filter_data[directory]:
result[directory][change] = [file for file in files if file.split('/')[-1] not in filter_data[directory][change]]

return result

@staticmethod
def perform_install_and_scan_for_aio(host_params, wazuh_params) -> None:
"""
Coordinates the action of install the Wazuh central components (AIO) and compares the checkfiles

Args:
host_params (str): host parameters
wazuh_params (str): wazuh parameters

"""
action_callback = lambda: WazuhCentralComponents._install_aio_callback(wazuh_params, host_params)
result = WazuhCentralComponents.perform_action_and_scan(host_params, action_callback)
logger.info(f'Pre and post install checkfile comparison in {HostInformation.get_os_name_and_version_from_inventory(host_params)}: {result}')
WazuhCentralComponents.assert_results(result)


@staticmethod
def perform_uninstall_and_scan_for_aio(host_params) -> None:
"""
Coordinates the action of uninstall the Wazuh central components (AIO) and compares the checkfiles

Args:
host_params (str): host parameters
wazuh_params (str): wazuh parameters

"""
action_callback = lambda: WazuhCentralComponents._uninstall_aio_callback(host_params)
result = WazuhCentralComponents.perform_action_and_scan(host_params, action_callback)
logger.info(f'Pre and post uninstall checkfile comparison in {HostInformation.get_os_name_and_version_from_inventory(host_params)}: {result}')
WazuhCentralComponents.assert_results(result)


@staticmethod
def assert_results(result) -> 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']
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}')
106 changes: 106 additions & 0 deletions deployability/modules/testing/tests/helpers/dashboard.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright (C) 2015, Wazuh Inc.
# Created by Wazuh, Inc. <[email protected]>.
# This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2

import requests
import json
import time

from .executor import ConnectionManager, WazuhAPI
from modules.testing.utils import logger


class WazuhDashboard:

@staticmethod
def get_dashboard_version(inventory_path) -> str:
"""
Returns Wazuh dashboard version

Args:
inventory_path (str): host's inventory path

Returns:
- str: Version of the Wazuh dashboard.
"""

return ConnectionManager.execute_commands(inventory_path,'cat /usr/share/wazuh-dashboard/VERSION').get('output').strip()


@staticmethod
def is_dashboard_active(inventory_path) -> bool:
"""
Returns True/False depending if the dashboard service is active or not

Args:
inventory_path (str): host's inventory path

Returns:
- bool: Status of the Wazuh dashboard service.
"""

return '200' in ConnectionManager.execute_commands(inventory_path, 'curl -Is -k https://localhost/app/login?nextUrl=%2F | head -n 1').get('output')


@staticmethod
def is_dashboard_keystore_working(inventory_path) -> bool:
"""
Returns True/False depending if the Wazuh dashboard keystore is active or not

Args:
inventory_path (str): host's inventory path

Returns:
- bool: Status of the Wazuh dashboard keystore.
"""

return 'No such file or directory' not in ConnectionManager.execute_commands(inventory_path, '/usr/share/wazuh-dashboard/bin/opensearch-dashboards-keystore list --allow-root').get('output')


@staticmethod
def are_dashboard_nodes_working(wazuh_api: WazuhAPI) -> str:
"""
Returns True/False depending the status of Wazuh dashboard nodes

Returns:
- bool: True/False depending on the status.
"""
response = requests.get(f"{wazuh_api.api_url}/api/status", auth=(wazuh_api.username, wazuh_api.password), verify=False)

result = True
if response.status_code == 200:
for status in json.loads((response.text))['status']['statuses']:
if status['state'] == 'green' or status['state'] == 'yellow':
result = True
else:
result = False
return result

else:
logger.error(f'The Wazuh dashboard API returned: {response.status_code}')

@staticmethod
def is_dashboard_port_open(inventory_path, wait=10, cycles=50):
"""
Check if the Wazuh dashboard port is open

Args:
inventory_path (str): Wazuh dashboard inventory.

Returns:
str: OS name.
"""
wait_cycles = 0
while wait_cycles < cycles:
ports = ConnectionManager.execute_commands(inventory_path, 'ss -t -a -n | grep ":443"').get('output') or ""
ports = ports.strip().split('\n')
for port in ports:
if any(state in port for state in ['ESTAB', 'LISTEN']):
continue
else:
time.sleep(wait)
wait_cycles += 1
break
else:
return True
return False
31 changes: 19 additions & 12 deletions deployability/modules/testing/tests/helpers/executor.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,28 +160,28 @@ def _execute_command(data, command) -> dict:


class WazuhAPI:
def __init__(self, inventory_path):
def __init__(self, inventory_path, component=None):
self.inventory_path = inventory_path
self.api_url = None
self.headers = None
self.component = component
self.username = None
self.password = None
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
self._authenticate()

def _extract_password(self, file_path, keyword):
if not 'true' in ConnectionManager.execute_commands(self.inventory_path, f'test -f {file_path} && echo "true" || echo "false"').get('output'):
ConnectionManager.execute_commands(self.inventory_path, 'tar -xvf wazuh-install-files.tar')
return ConnectionManager.execute_commands(self.inventory_path, f"grep {keyword} {file_path} | head -n 1 | awk '{{print $NF}}'").get('output').replace("'", "").replace("\n", "")

def _authenticate(self):
with open(self.inventory_path, 'r') as yaml_file:
inventory_data = yaml.safe_load(yaml_file)

user = 'wazuh'

#----Patch issue https://github.com/wazuh/wazuh-packages/issues/2883-------------
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 'true' not 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]
#--------------------------------------------------------------------------------
file_path = ConnectionManager.execute_commands(self.inventory_path, 'pwd').get('output').replace("\n", "") + '/wazuh-install-files/wazuh-passwords.txt'
password = self._extract_password(file_path, 'api_password')

login_endpoint = 'security/user/authenticate'
host = inventory_data.get('ansible_host')
Expand All @@ -193,8 +193,15 @@ def _authenticate(self):

token = json.loads(requests.post(login_url, headers=login_headers, verify=False).content.decode())['data']['token']

self.api_url = f'https://{host}:{port}'
self.headers = {
'Content-Type': 'application/json',
'Authorization': f'Bearer {token}'
}

self.api_url = f'https://{host}:{port}'

if self.component == 'dashboard' or self.component == 'indexer':
self.username = 'admin'
password = self._extract_password(file_path, 'indexer_password')
self.password = password
self.api_url = f'https://{host}' if self.component == 'dashboard' else f'https://127.0.0.1:9200'
Loading