From 8009f42548d799d05c81d82b7df958d8367db19d Mon Sep 17 00:00:00 2001 From: Enrique Araque Date: Mon, 17 Nov 2025 17:08:19 +0100 Subject: [PATCH] add pasword tool to the ami customizer service --- .../ami_post_configurer.py | 12 ++- .../templates/ami_custom_service_directory.j2 | 15 ++- ...ertificates.py => wazuh-ami-customizer.py} | 91 ++++++++++++++++--- .../scripts/wazuh-ami-customizer.service | 4 +- provisioner/main.py | 1 + provisioner/models/__init__.py | 1 + provisioner/models/certs_info.py | 2 +- provisioner/models/input.py | 13 ++- provisioner/models/password_tool_info.py | 30 ++++++ provisioner/models/utils/__init__.py | 2 +- provisioner/models/utils/file_formatter.py | 28 +++++- provisioner/provisioner.py | 78 ++++++++++------ tests/conftest.py | 1 + .../test_ami_post_configurer.py | 15 ++- tests/test_provisioner/test_provisioner.py | 44 +++++---- utils/__init__.py | 2 +- utils/enums.py | 8 +- 17 files changed, 267 insertions(+), 80 deletions(-) rename configurer/ami/ami_post_configurer/{custom_certificates.py => wazuh-ami-customizer.py} (75%) create mode 100644 provisioner/models/password_tool_info.py diff --git a/configurer/ami/ami_post_configurer/ami_post_configurer.py b/configurer/ami/ami_post_configurer/ami_post_configurer.py index 3fbfc5d..3af1a51 100644 --- a/configurer/ami/ami_post_configurer/ami_post_configurer.py +++ b/configurer/ami/ami_post_configurer/ami_post_configurer.py @@ -7,7 +7,7 @@ import paramiko from configurer.ami.ami_post_configurer.create_service_directory import create_directory_structure, generate_yaml from generic import exec_command, modify_file, remote_connection from models import Inventory -from utils import CertificatesComponent, Logger, RemoteDirectories +from utils import CertificatesComponent, Logger, PasswordToolComponent, RemoteDirectories logger = Logger("AmiPostConfigurer") @@ -15,21 +15,21 @@ logger = Logger("AmiPostConfigurer") @dataclass class AmiPostConfigurer: inventory: Inventory - environment_name: str = "certs-env" + environment_name: str = "customizer-env" enviroment_python_version: str = "3.11" custom_env_dependencies: ClassVar[list[str]] = [ "pydantic", "pyyaml", "paramiko", ] - custom_dir_name: str = "wazuh-ami-certs-customize" + custom_dir_name: str = "wazuh-ami-customizer" custom_dir_base_path: str = "/etc" cloud_instances_path: Path = Path("/var/lib/cloud/instances") journal_logs_path: Path = Path("/var/log/journal") journald__config_file_path: Path = Path("/etc/systemd/journald.conf") log_directory_path: Path = Path("/var/log") wazuh_indexer_log_path: Path = Path("/var/log/wazuh-indexer") - wazuh_server_log_path: Path = Path("/var/log/wazuh-server") + wazuh_server_log_path: Path = Path("/var/ossec/logs") wazuh_dashboard_log_path: Path = Path("/var/log/wazuh-dashboard") @remote_connection @@ -105,6 +105,8 @@ class AmiPostConfigurer: "remote_certs_path": RemoteDirectories.CERTS, "certs_tool": CertificatesComponent.CERTS_TOOL, "certs_config": CertificatesComponent.CONFIG, + "password_tool_path": RemoteDirectories.PASSWORD_TOOL, + "password_tool": PasswordToolComponent.PASSWORD_TOOL, } directory_template = generate_yaml( context=context, @@ -181,7 +183,7 @@ class AmiPostConfigurer: None """ - self.stop_service("wazuh-server", client=client) + self.stop_service("wazuh-manager", client=client) def remove_wazuh_indexes(self, client: paramiko.SSHClient) -> None: """ diff --git a/configurer/ami/ami_post_configurer/templates/ami_custom_service_directory.j2 b/configurer/ami/ami_post_configurer/templates/ami_custom_service_directory.j2 index 98b40d7..abf10a2 100644 --- a/configurer/ami/ami_post_configurer/templates/ami_custom_service_directory.j2 +++ b/configurer/ami/ami_post_configurer/templates/ami_custom_service_directory.j2 @@ -1,13 +1,22 @@ -name: "wazuh-ami-certs-customize" +# The structure contains name, files, and directories: +# - name: name of the directory to be created +# - files: list of files to be created. +# - path: path where the file to be copied is located. (not the destination path, but the source. The destination path is constructed automatically) +# - local: indicates whether the file is on the local machine (true) or on the remote instance (false). +# - directories: list of subdirectories to be created within the current directory. Each subdirectory can contain its own files and subdirectories. + +name: "wazuh-ami-customizer" files: - path: "{{ remote_certs_path }}/{{ certs_tool }}" local: false - path: "{{ remote_certs_path }}/{{ certs_config }}" local: false + - path: "{{ password_tool_path }}/{{ password_tool }}" + local: false directories: - - name: "custom_certificates" + - name: "src" files: - - path: "configurer/ami/ami_post_configurer/custom_certificates.py" + - path: "configurer/ami/ami_post_configurer/wazuh-ami-customizer.py" local: true directories: - name: "configurer" diff --git a/configurer/ami/ami_post_configurer/custom_certificates.py b/configurer/ami/ami_post_configurer/wazuh-ami-customizer.py similarity index 75% rename from configurer/ami/ami_post_configurer/custom_certificates.py rename to configurer/ami/ami_post_configurer/wazuh-ami-customizer.py index 5be22e0..d1151c3 100644 --- a/configurer/ami/ami_post_configurer/custom_certificates.py +++ b/configurer/ami/ami_post_configurer/wazuh-ami-customizer.py @@ -7,10 +7,12 @@ from configurer.core.utils import ComponentCertsDirectory from generic import exec_command from utils import Component, Logger -LOGFILE = Path("/var/log/wazuh-ami-custom-certificates.log") -TEMP_DIR = Path("/etc/wazuh-ami-certs-customize") +LOGFILE = Path("/var/log/wazuh-ami-customizer.log") +TEMP_DIR = Path("/etc/wazuh-ami-customizer") CERTS_TOOL_PATH = Path(f"{TEMP_DIR}/certs-tool.sh") CERTS_TOOL_CONFIG_PATH = Path(f"{TEMP_DIR}/config.yml") +PASSWORD_TOOL_PATH = Path(f"{TEMP_DIR}/password-tool.sh") +PASWORDS_FILE_NAME = Path("/etc/wazuh-ami-customizer/passwords.txt") SERVICE_PATH = "/etc/systemd/system" SERVICE_NAME = f"{SERVICE_PATH}/wazuh-ami-customizer.service" SERVICE_TIMER_NAME = f"{SERVICE_PATH}/wazuh-ami-customizer.timer" @@ -63,7 +65,7 @@ def stop_service(name: str) -> None: logger.debug(f"{name} service stopped") -def verify_component_connection(component: Component, command: str, retries: int = 5, wait_time: int = 5) -> None: +def verify_component_connection(component: Component, command: str, retries: int = 5, wait_time: int = 10) -> None: """ Verifies the component connection by sending a request to the component's endpoint. Args: @@ -194,13 +196,13 @@ def stop_components_services() -> None: logger.debug("Stopping Wazuh components services...") stop_service("wazuh-indexer") - stop_service("wazuh-server") + stop_service("wazuh-manager") stop_service("wazuh-dashboard") logger.debug("Wazuh components services stopped") -def verify_indexer_connection() -> None: +def verify_indexer_connection(password: str = "admin") -> None: """ Verifies the connection to the Wazuh indexer. This function sends a request to the Wazuh indexer endpoint and checks the response. @@ -210,7 +212,7 @@ def verify_indexer_connection() -> None: None """ - command = 'curl -XGET https://localhost:9200/ -uadmin:admin -k --max-time 120 --silent -w "%{http_code}" --output /dev/null' + command = f"curl -XGET https://localhost:9200/ -uadmin:{password} -k --max-time 120 --silent -w \"%{{http_code}}\" --output /dev/null" verify_component_connection(Component.WAZUH_INDEXER, command) @@ -259,8 +261,8 @@ def start_components_services() -> None: run_indexer_security_init() verify_indexer_connection() - enable_service("wazuh-server") - start_service("wazuh-server") + enable_service("wazuh-manager") + start_service("wazuh-manager") enable_service("wazuh-dashboard") start_service("wazuh-dashboard") @@ -269,6 +271,70 @@ def start_components_services() -> None: logger.debug("Wazuh components services started") +def get_instance_id() -> str: + """ + Retrieves the instance ID of the current machine capitalized. + + Returns: + str: The instance ID of the current machine. + """ + + logger.debug("Retrieving instance ID") + + command = "ec2-metadata | grep 'instance-id' | cut -d':' -f2" + output, error_output = exec_command(command=command) + if error_output: + logger.error(f"Error retrieving instance ID: {error_output}") + raise RuntimeError("Error retrieving instance ID") + return output.strip().capitalize() + + +def generate_password_file() -> None: + """ + Generates a password file using the password tool. + + Args: + path (Path): The path where the password file will be created. + + Returns: + None + """ + + logger.debug("Generating password file") + + command = f"bash {PASSWORD_TOOL_PATH} -gf {PASWORDS_FILE_NAME}" + _, error_output = exec_command(command=command) + if error_output: + logger.error(f"Error generating password file: {error_output}") + raise RuntimeError("Error generating password file") + + logger.debug("Password file generated") + + +def change_passwords() -> None: + logger.name = "CustomPasswords" + logger.debug("Changing passwords started") + logger.debug("Getting instance ID") + instance_id = get_instance_id() + + generate_password_file() + + logger.debug("Changing passwords to instance ID") + command = f""" + sudo sed -i 's/password:.*/password: {instance_id}/g' {PASWORDS_FILE_NAME} + bash {PASSWORD_TOOL_PATH} -a -A -au wazuh -ap wazuh -f {PASWORDS_FILE_NAME} + """ + + _, error_output = exec_command(command=command) + if error_output: + logger.error(f"Error changing passwords: {error_output}") + raise RuntimeError("Error changing passwords") + + logger.debug("Passwords changed. Verifying indexer connection with new password") + verify_indexer_connection(password=instance_id) + logger.debug("Changing passwords finished successfully") + + def clean_up() -> None: """ Cleans up temporary files and directories created during the process. @@ -301,11 +367,14 @@ if __name__ == "__main__": remove_certificates() create_certificates() start_components_services() + stop_service("wazuh-dashboard") + change_passwords() + start_service("wazuh-dashboard") start_ssh_service() clean_up() except Exception as e: - logger.error(f"An error occurred during the custom certificates configuration process: {e}") + logger.error(f"An error occurred during the customization process: {e}") start_ssh_service() - raise RuntimeError("An error occurred during the custom certificates configuration process") from e + raise RuntimeError("An error occurred during the customization process") from e - logger.info("Custom certificates configuration process finished") + logger.info("Customization process finished") diff --git a/configurer/ami/ami_pre_configurer/scripts/wazuh-ami-customizer.service b/configurer/ami/ami_pre_configurer/scripts/wazuh-ami-customizer.service index f1f3746..4f890e1 100644 --- a/configurer/ami/ami_pre_configurer/scripts/wazuh-ami-customizer.service +++ b/configurer/ami/ami_pre_configurer/scripts/wazuh-ami-customizer.service @@ -13,8 +13,8 @@ Wants=wazuh-ami-customizer.timer [Service] Type=oneshot -WorkingDirectory=/etc/wazuh-ami-certs-customize/custom_certificates -ExecStart=/etc/wazuh-ami-certs-customize/certs-env/bin/python -m custom_certificates +WorkingDirectory=/etc/wazuh-ami-customizer/src +ExecStart=/etc/wazuh-ami-customizer/customizer-env/bin/python -m wazuh-ami-customizer [Install] WantedBy=multi-user.target diff --git a/provisioner/main.py b/provisioner/main.py index fe19a0c..b92ff53 100644 --- a/provisioner/main.py +++ b/provisioner/main.py @@ -95,6 +95,7 @@ def main( Provisioner( inventory=input.inventory_content, certs=input.certificates_content, + password_tool=input.password_tool_url, components=components, arch=input.arch, package_type=input.package_type, diff --git a/provisioner/models/__init__.py b/provisioner/models/__init__.py index 9d3538d..f8515fd 100644 --- a/provisioner/models/__init__.py +++ b/provisioner/models/__init__.py @@ -3,3 +3,4 @@ from .component_info import ComponentInfo from .components_dependencies import ComponentsDependencies from .input import Input from .package_info import PackageInfo +from .password_tool_info import PasswordToolInfo diff --git a/provisioner/models/certs_info.py b/provisioner/models/certs_info.py index e4ea0a4..79384bd 100644 --- a/provisioner/models/certs_info.py +++ b/provisioner/models/certs_info.py @@ -40,7 +40,7 @@ class CertsInfo(BaseModel): """ logger.debug(f"Getting URL for {name}...") try: - url = AnyUrl(self.certs_url_content.get(name, None)) + url = AnyUrl(self.certs_url_content.get(name, None)) # type: ignore except pydantic_core._pydantic_core.ValidationError as err: raise ValueError(f"URL for {name} has an invalid format: {err}") from err diff --git a/provisioner/models/input.py b/provisioner/models/input.py index c8c4dd1..3708349 100644 --- a/provisioner/models/input.py +++ b/provisioner/models/input.py @@ -10,7 +10,8 @@ from utils import Component from .certs_info import CertsInfo from .components_dependencies import ComponentsDependencies from .package_info import PackageInfo -from .utils import format_certificates_urls_file, format_component_urls_file +from .password_tool_info import PasswordToolInfo +from .utils import format_certificates_urls_file, format_component_urls_file, format_password_tool_urls_file class Input(BaseModel): @@ -67,6 +68,16 @@ class Input(BaseModel): except FileNotFoundError as err: raise FileNotFoundError(f"Certificates file not found at {self.packages_url_path}") from err + @property + def password_tool_url(self) -> PasswordToolInfo: + try: + password_tool_data = format_password_tool_urls_file(self.packages_url_path) + if password_tool_data is None: + raise ValueError("Password tool URL not found in the packages URL file.") + return PasswordToolInfo(password_tool_url=password_tool_data) + except FileNotFoundError as err: + raise FileNotFoundError(f"Password tool file not found at {self.packages_url_path}") from err + @property def inventory_content(self, host_name: str | None = None) -> Inventory | None: return Inventory(self.inventory_path, host_name) if self.inventory_path else None diff --git a/provisioner/models/password_tool_info.py b/provisioner/models/password_tool_info.py new file mode 100644 index 0000000..0841e0c --- /dev/null +++ b/provisioner/models/password_tool_info.py @@ -0,0 +1,30 @@ +from pydantic import AnyUrl, BaseModel, field_validator + +from provisioner.utils import AllowedUrlHost +from utils import Logger + +from .utils import check_correct_url + +logger = Logger("Password-tool provision") + + +class PasswordToolInfo(BaseModel): + """ + PasswordToolInfo is a model that holds information about password tool URL. + + Attributes: + password_tool_url (str): A string containing the URL for the password-tool. + """ + + password_tool_url: AnyUrl + + @field_validator("password_tool_url") + def validate_password_tool_url(cls, url: AnyUrl) -> AnyUrl: + logger.debug("Validating password tool URL...") + if not check_correct_url( + url, + [AllowedUrlHost.RELEASE, AllowedUrlHost.PRE_RELEASE, AllowedUrlHost.INTERNAL], + ): + raise ValueError("URL for password-tool is not for Wazuh packages.") + + return url diff --git a/provisioner/models/utils/__init__.py b/provisioner/models/utils/__init__.py index 89a4097..3888be7 100644 --- a/provisioner/models/utils/__init__.py +++ b/provisioner/models/utils/__init__.py @@ -1,2 +1,2 @@ -from .file_formatter import format_certificates_urls_file, format_component_urls_file +from .file_formatter import format_certificates_urls_file, format_component_urls_file, format_password_tool_urls_file from .helpers import check_correct_url diff --git a/provisioner/models/utils/file_formatter.py b/provisioner/models/utils/file_formatter.py index e13eeb3..23f19a7 100644 --- a/provisioner/models/utils/file_formatter.py +++ b/provisioner/models/utils/file_formatter.py @@ -1,12 +1,13 @@ from pathlib import Path import yaml +from pydantic import AnyUrl from provisioner.utils import ( Component_arch, Package_type, ) -from utils import CertificatesComponent, Component +from utils import CertificatesComponent, Component, PasswordToolComponent def file_to_dict(raw_urls_path: Path) -> dict: @@ -157,6 +158,31 @@ def format_certificates_urls_file(raw_urls_path: Path) -> dict: return certificates_urls +def format_password_tool_urls_file(raw_urls_path: Path) -> AnyUrl | None: + """ + Formats a file containing raw URLs into a string of password tool URL. + + This function reads a file containing raw URLs and retrieves the URL + for the password tool. + + >>> raw_urls_path = Path("password_tool_urls.yaml") + >>> format_password_tool_urls_file(raw_urls_path) + 'https://packages.wazuh.com/password-tool-example/password_tool' + + Args: + raw_urls_path (Path): The path to the file containing the raw URLs. + + Returns: + str: The URL for the password tool. + """ + raw_urls_content = file_to_dict(raw_urls_path) + + for component_name, url in raw_urls_content.items(): + if PasswordToolComponent.PASSWORD_TOOL.name.lower() in component_name.lower(): + return AnyUrl(url) + return None + + def format_component_urls_file(raw_urls_path: Path) -> dict: """ Formats the component URLs file by processing raw URLs and organizing them by component, architecture, and type. diff --git a/provisioner/provisioner.py b/provisioner/provisioner.py index 57f7de7..66d05cf 100644 --- a/provisioner/provisioner.py +++ b/provisioner/provisioner.py @@ -5,9 +5,9 @@ from pydantic import AnyUrl from generic import exec_command, remote_connection from models import Inventory -from provisioner.models import CertsInfo, ComponentInfo +from provisioner.models import CertsInfo, ComponentInfo, PasswordToolInfo from provisioner.utils import Component_arch, Package_manager, Package_type -from utils import CertificatesComponent, Component, Logger, RemoteDirectories +from utils import CertificatesComponent, Component, Logger, PasswordToolComponent, RemoteDirectories logger = Logger("Provisioner") @@ -30,6 +30,7 @@ class Provisioner: inventory: Inventory | None certs: CertsInfo + password_tool: PasswordToolInfo components: list[ComponentInfo] arch: Component_arch = Component_arch.X86_64 package_type: Package_type = Package_type.RPM @@ -49,7 +50,8 @@ class Provisioner: 1. Logs the start of the provisioning process. 2. Provisions the certs_tool using `certs_tool_provision`. 3. Provision the config file using `certs_config_provision`. - 4. Iterates over each component and performs the following: + 4. Provisions the password tool using `password_tool_provision`. + 5. Iterates over each component and performs the following: a. Logs the start of provisioning for the component. b. Provisions dependencies for the component using `dependencies_provision`. c. Provisions packages for the component using `packages_provision`. @@ -62,6 +64,10 @@ class Provisioner: self.certs_tool_provision(client) self.certs_config_provision(client) + + logger.debug_title("Provisioning password tool") + + self.password_tool_provision(client) logger.debug_title("Provisioning special dependencies") @@ -77,20 +83,22 @@ class Provisioner: Provisions the certs_tool on the specified client. This method uses the provided SSH client to connect to a remote machine - and provision the certs_tool by calling the `certificates_provision` + and provision the certs_tool by calling the `tool_provision` method with the appropriate URL. Args: client (paramiko.SSHClient): The SSH client used to connect to the remote machine. """ - self.certificates_provision(self.certs.certs_tool_url, CertificatesComponent.CERTS_TOOL, client) + self.tool_provision( + self.certs.certs_tool_url, f"{RemoteDirectories.CERTS}", CertificatesComponent.CERTS_TOOL, client + ) def certs_config_provision(self, client: paramiko.SSHClient | None = None) -> None: """ Provisions the certs config file on the remote client. This method uses the provided SSH client to connect to a remote machine - and provision the certs config file by calling the `certificates_provision` + and provision the certs config file by calling the `tool_provision` method with the appropriate URL. Args: @@ -99,7 +107,25 @@ class Provisioner: Returns: None """ - self.certificates_provision(self.certs.config_url, CertificatesComponent.CONFIG, client) + self.tool_provision(self.certs.config_url, f"{RemoteDirectories.CERTS}", CertificatesComponent.CONFIG, client) + + def password_tool_provision(self, client: paramiko.SSHClient | None = None) -> None: + """ + Provisions the password tool on the specified client. + + This method uses the provided SSH client to connect to a remote machine + and provision the password tool by calling the `tool_provision` + method with the appropriate URL. + + Args: + client (paramiko.SSHClient): The SSH client used to connect to the remote machine. + """ + self.tool_provision( + self.password_tool.password_tool_url, + f"{RemoteDirectories.PASSWORD_TOOL}", + PasswordToolComponent.PASSWORD_TOOL, + client, + ) def special_dependencies_provision(self, client: paramiko.SSHClient | None = None) -> None: """ @@ -177,39 +203,33 @@ class Provisioner: component.name.replace("_", " ").capitalize(), ) - def certificates_provision( - self, certs_file_url: AnyUrl, filename: str, client: paramiko.SSHClient | None = None + def tool_provision( + self, tool_url: AnyUrl, tool_dir: str, tool_filename: str, client: paramiko.SSHClient | None = None ) -> None: """ - Downloads a certificate file (certs_tool or config) from a given URL and saves it to a remote directory on a server. - + Provisions a tool (certs_tool, config file, or password tool) on the remote client. Args: - certs_file_url (AnyUrl): The URL of the certificate file to be downloaded. - client (paramiko.SSHClient): An active SSH client connected to the remote server. - - Raises: - Exception: If there is an error during the download process. - - Logs: - Error message if the download fails. - Success message if the download is successful. + tool_url (AnyUrl): The URL of the tool to be provisioned. + tool_dir (str): The directory on the remote client where the tool will be stored. + tool_filename (str): The filename of the tool to be provisioned. + client (paramiko.SSHClient): The SSH client used to connect to the remote machine. """ - logger.debug(f"Provisioning {filename}") + logger.debug(f"Provisioning {tool_filename}") - command_template = "mkdir -p {dir} && curl -s -o {path} '{certs_file_url}'" + command_template = "mkdir -p {directory} && curl -s -o {path} '{tool_file_url}'" command = command_template.format( - dir=f"{RemoteDirectories.CERTS}", - path=f"{RemoteDirectories.CERTS}/{filename}", - certs_file_url=certs_file_url, + directory=tool_dir, + path=f"{tool_dir}/{tool_filename}", + tool_file_url=tool_url, ) - output, error_output = exec_command(command=command, client=client) + _, error_output = exec_command(command=command, client=client) if error_output: - logger.error(f"Error downloading {filename}: {error_output}") - raise RuntimeError(f"Error downloading {filename}") + logger.error(f"Error downloading {tool_filename}: {error_output}") + raise RuntimeError(f"Error downloading {tool_filename}") - logger.info_success(f"{filename} downloaded successfully") + logger.info_success(f"{tool_filename} downloaded successfully") def list_dependencies(self, elements: list[str], component_name: str) -> None: """ diff --git a/tests/conftest.py b/tests/conftest.py index 4201dab..416aa47 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -41,6 +41,7 @@ def mock_logger(autouse=True): logger_paths = [ "provisioner.provisioner.logger", "provisioner.models.certs_info.logger", + "provisioner.models.password_tool_info.logger", "generic.remote_connection.logger", "configurer.core.models.wazuh_components_config_manager.logger", "configurer.core.models.certificates_manager.logger", diff --git a/tests/test_configurer/test_ami/test_ami_post_configurer/test_ami_post_configurer.py b/tests/test_configurer/test_ami/test_ami_post_configurer/test_ami_post_configurer.py index 15736f3..ac2783d 100644 --- a/tests/test_configurer/test_ami/test_ami_post_configurer/test_ami_post_configurer.py +++ b/tests/test_configurer/test_ami/test_ami_post_configurer/test_ami_post_configurer.py @@ -4,7 +4,7 @@ from unittest.mock import MagicMock, patch import pytest from configurer.ami.ami_post_configurer.ami_post_configurer import AmiPostConfigurer -from utils.enums import CertificatesComponent, RemoteDirectories +from utils.enums import CertificatesComponent, PasswordToolComponent, RemoteDirectories @pytest.fixture() @@ -78,6 +78,8 @@ def test_create_custom_dir_success(mock_create_structure, mock_generate_yaml, mo "remote_certs_path": RemoteDirectories.CERTS, "certs_tool": CertificatesComponent.CERTS_TOOL, "certs_config": CertificatesComponent.CONFIG, + "password_tool_path": RemoteDirectories.PASSWORD_TOOL, + "password_tool": PasswordToolComponent.PASSWORD_TOOL, } mock_generate_yaml.return_value = {"template": "test_value"} @@ -145,11 +147,11 @@ def test_stop_service_fails(mock_ami_post_configurer, mock_exec_command, mock_pa def test_stop_wazuh_server(mock_ami_post_configurer, mock_exec_command, mock_paramiko, mock_logger): mock_ami_post_configurer.stop_wazuh_server(mock_paramiko.return_value) - command = "sudo systemctl stop wazuh-server" + command = "sudo systemctl stop wazuh-manager" mock_exec_command.assert_called_once_with(command=command, client=mock_paramiko.return_value) - mock_logger.debug.assert_called_once_with("Stopping wazuh-server service") - mock_logger.info_success.assert_called_once_with("wazuh-server service stopped successfully") + mock_logger.debug.assert_called_once_with("Stopping wazuh-manager service") + mock_logger.info_success.assert_called_once_with("wazuh-manager service stopped successfully") def test_stop_wazuh_indexer(mock_ami_post_configurer, mock_exec_command, mock_paramiko, mock_logger): @@ -176,7 +178,9 @@ def test_stop_wazuh_indexer(mock_ami_post_configurer, mock_exec_command, mock_pa def test_remove_wazuh_indexes(mock_ami_post_configurer, mock_exec_command, mock_paramiko, mock_logger): mock_ami_post_configurer.remove_wazuh_indexes(mock_paramiko.return_value) - command = 'sudo curl -s -o /dev/null -w "%{http_code}" -X DELETE -u "admin:admin" -k "https://127.0.0.1:9200/wazuh-*"' + command = ( + 'sudo curl -s -o /dev/null -w "%{http_code}" -X DELETE -u "admin:admin" -k "https://127.0.0.1:9200/wazuh-*"' + ) mock_exec_command.assert_called_once_with(command=command, client=mock_paramiko.return_value) @@ -192,6 +196,7 @@ def test_remove_wazuh_indexes_fail(mock_ami_post_configurer, mock_exec_command, mock_logger.error.assert_called_once_with("Error removing wazuh- indexes") + def test_run_security_init_script(mock_ami_post_configurer, mock_exec_command, mock_paramiko, mock_logger): mock_ami_post_configurer.run_security_init_script(mock_paramiko.return_value) diff --git a/tests/test_provisioner/test_provisioner.py b/tests/test_provisioner/test_provisioner.py index 8f02509..f639eb0 100644 --- a/tests/test_provisioner/test_provisioner.py +++ b/tests/test_provisioner/test_provisioner.py @@ -6,6 +6,7 @@ from pydantic import AnyUrl from provisioner.models.certs_info import CertsInfo from provisioner.models.component_info import ComponentInfo +from provisioner.models.password_tool_info import PasswordToolInfo from provisioner.provisioner import Provisioner from provisioner.utils import Package_manager, Package_type from utils.enums import Component @@ -32,10 +33,13 @@ def component_info_valid(valid_inventory): "config": "http://packages-dev.wazuh.com/example/config.yml", } ) + password_tool = PasswordToolInfo(password_tool_url=AnyUrl("http://packages-dev.wazuh.com/example/password-tool.sh")) + package_type = Package_type.RPM return Provisioner( inventory=valid_inventory, certs=certs, + password_tool=password_tool, components=[component_server], package_type=package_type, ) @@ -56,9 +60,10 @@ def test_provision_success(mock_paramiko, mock_logger, component_info_valid, moc mock_client_instance = MagicMock() mock_paramiko.return_value = mock_client_instance - certs_expect_commands = [ - "mkdir -p ~/wazuh-configure/certs && curl -s -o ~/wazuh-configure/certs/certs-tool.sh 'http://packages-dev.wazuh.com/example/certs-tool.sh'", - "mkdir -p ~/wazuh-configure/certs && curl -s -o ~/wazuh-configure/certs/config.yml 'http://packages-dev.wazuh.com/example/config.yml'", + tools_expect_commands = [ + "mkdir -p ~/wazuh-configure/tools/certs && curl -s -o ~/wazuh-configure/tools/certs/certs-tool.sh 'http://packages-dev.wazuh.com/example/certs-tool.sh'", + "mkdir -p ~/wazuh-configure/tools/certs && curl -s -o ~/wazuh-configure/tools/certs/config.yml 'http://packages-dev.wazuh.com/example/config.yml'", + "mkdir -p ~/wazuh-configure/tools && curl -s -o ~/wazuh-configure/tools/password-tool.sh 'http://packages-dev.wazuh.com/example/password-tool.sh'" ] dependencies_expect_commands = [ @@ -85,22 +90,22 @@ def test_provision_success(mock_paramiko, mock_logger, component_info_valid, moc key_filename=str(component_info_valid.inventory.ansible_ssh_private_key_file), ) - assert mock_exec_command.call_count == 7 # 3 for dependencies, 2 for certs, 1 download package, 1 install package + assert mock_exec_command.call_count == 8 # 3 for dependencies, 3 for tools (certs-tool and password-tool), 1 download package, 1 install package - # certs - assert certs_expect_commands[0] in mock_exec_command.call_args_list[0].kwargs["command"] - assert certs_expect_commands[1] in mock_exec_command.call_args_list[1].kwargs["command"] + # tools + assert tools_expect_commands[0] in mock_exec_command.call_args_list[0].kwargs["command"] + assert tools_expect_commands[1] in mock_exec_command.call_args_list[1].kwargs["command"] + assert tools_expect_commands[2] in mock_exec_command.call_args_list[2].kwargs["command"] # dependencies - assert dependencies_expect_commands[0] in mock_exec_command.call_args_list[2].kwargs["command"] - assert dependencies_expect_commands[1] in mock_exec_command.call_args_list[2].kwargs["command"] - assert dependencies_expect_commands[2] in mock_exec_command.call_args_list[3].kwargs["command"] - assert dependencies_expect_commands[3] in mock_exec_command.call_args_list[4].kwargs["command"] + assert dependencies_expect_commands[0] in mock_exec_command.call_args_list[3].kwargs["command"] + assert dependencies_expect_commands[1] in mock_exec_command.call_args_list[3].kwargs["command"] + assert dependencies_expect_commands[2] in mock_exec_command.call_args_list[4].kwargs["command"] + assert dependencies_expect_commands[3] in mock_exec_command.call_args_list[5].kwargs["command"] # package - assert package_expect_commands[0] in mock_exec_command.call_args_list[5].kwargs["command"] - assert package_expect_commands[1] in mock_exec_command.call_args_list[6].kwargs["command"] - + assert package_expect_commands[0] in mock_exec_command.call_args_list[6].kwargs["command"] + assert package_expect_commands[1] in mock_exec_command.call_args_list[7].kwargs["command"] mock_logger.debug_title.assert_any_call("Starting provisioning") mock_logger.debug_title.assert_any_call("Provisioning certificates files") mock_logger.debug_title.assert_any_call("Starting provisioning for wazuh manager") @@ -120,7 +125,7 @@ def test_certs_tool_provision_success( getattr(component_info_valid, certs_method)(mock_client_instance) mock_exec_command.assert_called_once_with( - command=f"mkdir -p ~/wazuh-configure/certs && curl -s -o ~/wazuh-configure/certs/{certs_component} 'http://packages-dev.wazuh.com/example/{certs_component}'", + command=f"mkdir -p ~/wazuh-configure/tools/certs && curl -s -o ~/wazuh-configure/tools/certs/{certs_component} 'http://packages-dev.wazuh.com/example/{certs_component}'", client=mock_client_instance, ) mock_logger.debug.assert_any_call(f"Provisioning {certs_component}") @@ -142,7 +147,7 @@ def test_certs_tool_provision_failure( getattr(component_info_valid, certs_method)(mock_client_instance) mock_exec_command.assert_called_once_with( - command=f"mkdir -p ~/wazuh-configure/certs && curl -s -o ~/wazuh-configure/certs/{certs_component} 'http://packages-dev.wazuh.com/example/{certs_component}'", + command=f"mkdir -p ~/wazuh-configure/tools/certs && curl -s -o ~/wazuh-configure/tools/certs/{certs_component} 'http://packages-dev.wazuh.com/example/{certs_component}'", client=mock_client_instance, ) mock_logger.debug.assert_any_call(f"Provisioning {certs_component}") @@ -345,9 +350,10 @@ def test_install_package( if "installed successfully" in expected_log and "WARNING" not in error_output: mock_logger.info_success.assert_called_once_with(f"{package_name} {expected_log}") elif "is already installed" in expected_log: - mock_logger.debug.assert_has_calls( - [mock.call(f"Installing {package_name}"), mock.call(f"{package_name} {expected_log}")] - ) + mock_logger.debug.assert_has_calls([ + mock.call(f"Installing {package_name}"), + mock.call(f"{package_name} {expected_log}"), + ]) elif "installed successfully" in expected_log and "WARNING" in error_output: mock_logger.warning.assert_called_once_with(f"{error_output}") mock_logger.info_success.assert_called_once_with(f"{package_name} {expected_log}") diff --git a/utils/__init__.py b/utils/__init__.py index db2e87b..bca3386 100644 --- a/utils/__init__.py +++ b/utils/__init__.py @@ -1,2 +1,2 @@ -from .enums import CertificatesComponent, Component, RemoteDirectories +from .enums import CertificatesComponent, Component, PasswordToolComponent, RemoteDirectories from .logger import Logger diff --git a/utils/enums.py b/utils/enums.py index 17095c9..80717df 100644 --- a/utils/enums.py +++ b/utils/enums.py @@ -8,6 +8,10 @@ class Component(StrEnum): ALL = auto() +class PasswordToolComponent(StrEnum): + PASSWORD_TOOL = "password-tool.sh" + + class CertificatesComponent(StrEnum): CERTS_TOOL = "certs-tool.sh" CONFIG = "config.yml" @@ -15,5 +19,7 @@ class CertificatesComponent(StrEnum): class RemoteDirectories(StrEnum): WAZUH_ROOT_DIR = "~/wazuh-configure" + TOOLS_DIR = f"{WAZUH_ROOT_DIR}/tools" PACKAGES = f"{WAZUH_ROOT_DIR}/packages" - CERTS = f"{WAZUH_ROOT_DIR}/certs" + CERTS = f"{TOOLS_DIR}/certs" + PASSWORD_TOOL = f"{TOOLS_DIR}"