From 45b97d817fb8d177189fb5f678db719bf4ed9393 Mon Sep 17 00:00:00 2001 From: GGP1 Date: Mon, 22 Jul 2024 15:20:21 -0300 Subject: [PATCH 1/8] Agent comms API initial setup --- apis/comms_api/scripts/wazuh_comms_apid.py | 212 +++++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100755 apis/comms_api/scripts/wazuh_comms_apid.py diff --git a/apis/comms_api/scripts/wazuh_comms_apid.py b/apis/comms_api/scripts/wazuh_comms_apid.py new file mode 100755 index 00000000000..4a16b5e3d48 --- /dev/null +++ b/apis/comms_api/scripts/wazuh_comms_apid.py @@ -0,0 +1,212 @@ +import logging +import logging.config +import os +import signal +import ssl +from argparse import ArgumentParser, Namespace +from functools import partial +from sys import exit +from typing import Any, Callable, Dict + +from fastapi import FastAPI +from gunicorn.app.base import BaseApplication + +from api.alogging import set_logging +from api.configuration import generate_private_key, generate_self_signed_certificate +from api.constants import COMMS_API_LOG_PATH +from routers import router +from wazuh.core import common, pyDaemonModule, utils +from wazuh.core.exception import WazuhCommsAPIError + +MAIN_PROCESS = 'wazuh-comms-apid' + +app = FastAPI() +app.include_router(router.router) + + +def setup_logging(foreground_mode: bool) -> dict: + """Sets up the logging module and returns the configuration used. + + Parameters + ---------- + foreground_mode : bool + Whether to execute the script in foreground mode or not. + + Returns + ------- + dict + Logging configuration dictionary. + """ + log_config_dict = set_logging(log_filepath=COMMS_API_LOG_PATH, + log_level='INFO', + foreground_mode=foreground_mode) + + for handler in log_config_dict['handlers'].values(): + if 'filename' in handler: + utils.assign_wazuh_ownership(handler['filename']) + os.chmod(handler['filename'], 0o660) + + logging.config.dictConfig(log_config_dict) + + return log_config_dict + +def configure_ssl(keyfile: str, certfile: str) -> None: + """Generate SSL key file and self-siged certificate if they do not exist. + + Raises + ------ + ssl.SSLError + Invalid private key. + IOError + File permissions or path error. + """ + try: + if not os.path.exists(keyfile) or not os.path.exists(certfile): + private_key = generate_private_key(keyfile) + logger.info(f"Generated private key file in {certfile}") + + generate_self_signed_certificate(private_key, certfile) + logger.info(f"Generated certificate file in {certfile}") + except ssl.SSLError as exc: + raise WazuhCommsAPIError(2700, extra_message=str(exc)) + except IOError as exc: + if exc.errno == 22: + raise WazuhCommsAPIError(2701, extra_message=str(exc)) + elif exc.errno == 13: + raise WazuhCommsAPIError(2702, extra_message=str(exc)) + else: + raise WazuhCommsAPIError(2703, extra_message=str(exc)) + +def ssl_context(conf, default_ssl_context_factory) -> ssl.SSLContext: + """Returns the default SSL context with a custom minimum version. + + Returns + ------- + ssl.SSLContext + Server SSL context. + """ + context = default_ssl_context_factory() + context.minimum_version = ssl.TLSVersion.MINIMUM_SUPPORTED + return context + +def get_gunicorn_options(pid: int, foreground_mode: bool, log_config_dict: dict) -> dict: + """Get the gunicorn app configuration options. + + Parameters + ---------- + pid : int + Main process ID. + foreground_mode : bool + Whether to execute the script in foreground mode or not. + log_config_dict : dict + Logging configuration dictionary. + + Returns + ------- + dict + Gunicorn configuration options. + """ + # TODO: get values from the configuration + keyfile = '/var/ossec/api/configuration/ssl/server.key' + certfile = '/var/ossec/api/configuration/ssl/server.crt' + configure_ssl(keyfile, certfile) + + pidfile = os.path.join(common.WAZUH_PATH, common.OS_PIDFILE_PATH, f'{MAIN_PROCESS}-{pid}.pid') + + return { + 'proc_name': MAIN_PROCESS, + 'pidfile': pidfile, + 'daemon': not foreground_mode, + 'bind': f'{args.host}:{args.port}', + 'workers': 4, + 'worker_class': 'uvicorn.workers.UvicornWorker', + 'preload_app': True, + 'keyfile': keyfile, + 'certfile': certfile, + 'ca_certs': '/etc/ssl/certs/ca-certificates.crt', + 'ssl_context': ssl_context, + 'ciphers': '', + 'logconfig_dict': log_config_dict, + 'user': os.getuid() + } + +def get_script_arguments() -> Namespace: + """Get script arguments. + + Returns + ------- + argparse.Namespace + Arguments passed to the script. + """ + parser = ArgumentParser() + parser.add_argument('--host', type=str, default='0.0.0.0', help='API host.') + parser.add_argument('-p', '--port', type=int, default=27000, help='API port.') + parser.add_argument('-f', action='store_true', dest='foreground', help='Run API in foreground mode.') + parser.add_argument('-r', action='store_true', dest='root', help='Run as root') + parser.add_argument('-t', action='store_true', dest='test_config', help='Test configuration') + + return parser.parse_args() + + +class StandaloneApplication(BaseApplication): + def __init__(self, app: Callable, options: Dict[str, Any] = None): + self.options = options or {} + self.app = app + super().__init__() + + def load_config(self): + config = { + key: value + for key, value in self.options.items() + if key in self.cfg.settings and value is not None + } + for key, value in config.items(): + self.cfg.set(key.lower(), value) + + def load(self): + return self.app + + +if __name__ == '__main__': + args = get_script_arguments() + + # The bash script that starts all services first executes them using the `-t` flag to check the configuration. + # We don't have a configuration yet, but it will be added in the future, so we just exit successfully for now. + # + # TODO: check configuration + if args.test_config: + exit(0) + + utils.clean_pid_files(MAIN_PROCESS) + + log_config_dict = setup_logging(args.foreground) + logger = logging.getLogger('wazuh-comms-api') + + if args.foreground: + logger.info('Starting API in foreground') + else: + pyDaemonModule.pyDaemon() + + if not args.root: + # Drop privileges to wazuh + os.setgid(common.wazuh_gid()) + os.setuid(common.wazuh_uid()) + else: + logger.info('Starting API as root') + + pid = os.getpid() + signal.signal(signal.SIGTERM, partial(pyDaemonModule.exit_handler, process_name=MAIN_PROCESS, logger=logger)) + signal.signal(signal.SIGINT, signal.SIG_IGN) + + try: + options = get_gunicorn_options(pid, args.foreground, log_config_dict) + StandaloneApplication(app, options).run() + except WazuhCommsAPIError as e: + logger.error(f'Error when trying to start the Wazuh Agent comms API. {e}') + exit(1) + except Exception as e: + logger.error(f'Internal error when trying to start the Wazuh Agent comms API. {e}') + exit(1) + finally: + pyDaemonModule.delete_child_pids(MAIN_PROCESS, pid, logger) + pyDaemonModule.delete_pid(MAIN_PROCESS, pid) From 68d61be30a77cd3ae29db95961b0eb6fd9950e18 Mon Sep 17 00:00:00 2001 From: GGP1 Date: Mon, 22 Jul 2024 15:20:21 -0300 Subject: [PATCH 2/8] Agent comms API initial setup --- apis/.gitignore | 15 ++++++++++++ apis/comms_api/Makefile | 31 ++++++++++++++++++++++++ apis/comms_api/README.md | 3 +++ apis/comms_api/__init__.py | 0 apis/comms_api/routers/router.py | 10 ++++++++ apis/comms_api/setup.py | 33 ++++++++++++++++++++++++++ apis/wrappers/generic_wrapper.sh | 25 +++++++++++++++++++ framework/requirements.txt | 6 +++++ framework/wazuh/core/cluster/utils.py | 3 ++- framework/wazuh/core/pyDaemonModule.py | 6 +++++ framework/wazuh/tests/test_manager.py | 2 +- src/Makefile | 5 +++- src/init/inst-functions.sh | 5 +++- tools/bump_version.sh | 5 ++++ 14 files changed, 145 insertions(+), 4 deletions(-) create mode 100644 apis/.gitignore create mode 100644 apis/comms_api/Makefile create mode 100644 apis/comms_api/README.md create mode 100644 apis/comms_api/__init__.py create mode 100644 apis/comms_api/routers/router.py create mode 100644 apis/comms_api/setup.py create mode 100644 apis/wrappers/generic_wrapper.sh diff --git a/apis/.gitignore b/apis/.gitignore new file mode 100644 index 00000000000..951d51159e3 --- /dev/null +++ b/apis/.gitignore @@ -0,0 +1,15 @@ +# Python +*.pyc +*.egg-info +*.egg + +# Tests +.env/configurations/tmp/* + +# Framework +dist/ +build/ + +# Projects +.idea +.code diff --git a/apis/comms_api/Makefile b/apis/comms_api/Makefile new file mode 100644 index 00000000000..60aed8dfd63 --- /dev/null +++ b/apis/comms_api/Makefile @@ -0,0 +1,31 @@ +# Makefile for Wazuh APIs +# Copyright (C) 2015, Wazuh Inc. +# May 3, 2017 +# +# Syntax: make [ all | install | service ] + +WAZUH_GROUP = wazuh +INSTALLDIR ?= /var/ossec + +MV_FILE = mv -f +INSTALL_DIR = install -o root -g ${WAZUH_GROUP} -m 0750 -d +INSTALL_EXEC = install -o root -g ${WAZUH_GROUP} -m 0750 +INSTALL_FILE = install -o root -g ${WAZUH_GROUP} -m 0640 + + +.PHONY: all install + +all: install + +install: + # Copy files and create folders + $(INSTALL_DIR) $(INSTALLDIR) + + $(INSTALL_DIR) $(INSTALLDIR)/apis/scripts + $(INSTALL_FILE) scripts/wazuh_comms_apid.py ${INSTALLDIR}/apis/scripts + + # Install scripts/%.py on $(INSTALLDIR)/bin/% + $(foreach script,$(wildcard scripts/*.py),$(INSTALL_EXEC) ../wrappers/generic_wrapper.sh $(patsubst scripts/%.py,$(INSTALLDIR)/bin/%,$(script));) + + $(MV_FILE) $(INSTALLDIR)/bin/wazuh_comms_apid $(INSTALLDIR)/bin/wazuh-comms-apid + diff --git a/apis/comms_api/README.md b/apis/comms_api/README.md new file mode 100644 index 00000000000..e83e80d306a --- /dev/null +++ b/apis/comms_api/README.md @@ -0,0 +1,3 @@ +# Agent communications API + +The Agent communications API is an open source RESTful API that allows for interaction with the Wazuh agents. diff --git a/apis/comms_api/__init__.py b/apis/comms_api/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/apis/comms_api/routers/router.py b/apis/comms_api/routers/router.py new file mode 100644 index 00000000000..ff47cb71350 --- /dev/null +++ b/apis/comms_api/routers/router.py @@ -0,0 +1,10 @@ +from fastapi import APIRouter, status +from fastapi.responses import Response + + +router = APIRouter(prefix='/api/v1') + + +@router.get("/") +async def home(): + return Response(status_code=status.HTTP_200_OK) diff --git a/apis/comms_api/setup.py b/apis/comms_api/setup.py new file mode 100644 index 00000000000..79c4c7ed023 --- /dev/null +++ b/apis/comms_api/setup.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python + +# Copyright (C) 2015, Wazuh Inc. +# Created by Wazuh, Inc. . +# This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2 + +from setuptools import setup, find_namespace_packages + +# To install the library, run the following +# +# python setup.py install +# +# prerequisite: setuptools +# http://pypi.python.org/pypi/setuptools + +setup( + name='comms_api', + version='5.0.0', + description='Agent communications API', + author_email='hello@wazuh.com', + author='Wazuh', + url='https://github.com/wazuh', + keywords=['Agent communications API', 'Agent comms API'], + install_requires=[], + packages=find_namespace_packages(exclude=['*.test', '*.test.*', 'test.*', 'test']), + package_data={}, + include_package_data=True, + zip_safe=False, + license='GPLv2', + long_description=""" + The Agent communications API is an open source RESTful API that allows for interaction with the Wazuh agents. + """ +) diff --git a/apis/wrappers/generic_wrapper.sh b/apis/wrappers/generic_wrapper.sh new file mode 100644 index 00000000000..1d50f6dec9a --- /dev/null +++ b/apis/wrappers/generic_wrapper.sh @@ -0,0 +1,25 @@ +#!/bin/sh +# Copyright (C) 2015, Wazuh Inc. +# Created by Wazuh, Inc. . +# This program is a free software; you can redistribute it and/or modify it under the terms of GPLv2 + +WPYTHON_BIN="framework/python/bin/python3" + +SCRIPT_PATH_NAME="$0" + +DIR_NAME="$(cd $(dirname ${SCRIPT_PATH_NAME}); pwd -P)" +SCRIPT_NAME="$(basename ${SCRIPT_PATH_NAME})" + + +case ${DIR_NAME} in + */bin) + if [ -z "${WAZUH_PATH}" ]; then + WAZUH_PATH="$(cd ${DIR_NAME}/..; pwd)" + fi + + PYTHON_SCRIPT="${WAZUH_PATH}/apis/scripts/$(echo ${SCRIPT_NAME} | sed 's/\-/_/g').py" + ;; +esac + + +${WAZUH_PATH}/${WPYTHON_BIN} ${PYTHON_SCRIPT} $@ diff --git a/framework/requirements.txt b/framework/requirements.txt index 4257d0909e0..e27233e53df 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -102,3 +102,9 @@ xmltodict==0.12.0 yarl==1.7.0 zipp==3.3.2 content_size_limit_asgi==0.1.5 + +# Agent comms API +# TODO: define versions +fastapi +gunicorn +pydantic diff --git a/framework/wazuh/core/cluster/utils.py b/framework/wazuh/core/cluster/utils.py index 866c68d4be7..45e19f5890a 100644 --- a/framework/wazuh/core/cluster/utils.py +++ b/framework/wazuh/core/cluster/utils.py @@ -265,7 +265,8 @@ def get_manager_status(cache=False) -> typing.Dict: processes = ['wazuh-agentlessd', 'wazuh-analysisd', 'wazuh-authd', 'wazuh-csyslogd', 'wazuh-dbd', 'wazuh-monitord', 'wazuh-execd', 'wazuh-integratord', 'wazuh-logcollector', 'wazuh-maild', 'wazuh-remoted', - 'wazuh-reportd', 'wazuh-syscheckd', 'wazuh-clusterd', 'wazuh-modulesd', 'wazuh-db', 'wazuh-apid'] + 'wazuh-reportd', 'wazuh-syscheckd', 'wazuh-clusterd', 'wazuh-modulesd', 'wazuh-db', 'wazuh-apid', + 'wazuh-comms-apid'] data, pidfile_regex, run_dir = {}, re.compile(r'.+\-(\d+)\.pid$'), os.path.join(common.WAZUH_PATH, "var", "run") for process in processes: diff --git a/framework/wazuh/core/pyDaemonModule.py b/framework/wazuh/core/pyDaemonModule.py index 49f7aa20778..096654f4e46 100644 --- a/framework/wazuh/core/pyDaemonModule.py +++ b/framework/wazuh/core/pyDaemonModule.py @@ -129,3 +129,9 @@ def delete_child_pids(name: str, ppid: int, logger: logging.Logger): except OSError: pass filenames.remove(filename) + +def exit_handler(signum, frame, process_name: str, logger: logging.Logger) -> None: + """Try to kill API child processes and remove their PID files.""" + pid = os.getpid() + delete_child_pids(process_name, pid, logger) + delete_pid(process_name, pid) diff --git a/framework/wazuh/tests/test_manager.py b/framework/wazuh/tests/test_manager.py index 4bf3561e781..2f3910340b1 100644 --- a/framework/wazuh/tests/test_manager.py +++ b/framework/wazuh/tests/test_manager.py @@ -63,7 +63,7 @@ def test_manager(): 'wazuh-execd': 'running', 'wazuh-integratord': 'running', 'wazuh-logcollector': 'running', 'wazuh-maild': 'running', 'wazuh-remoted': 'running', 'wazuh-reportd': 'running', 'wazuh-syscheckd': 'running', 'wazuh-clusterd': 'running', 'wazuh-modulesd': 'running', - 'wazuh-db': 'running', 'wazuh-apid': 'running'} + 'wazuh-db': 'running', 'wazuh-apid': 'running', 'wazuh-comms-apid': 'running'} @patch('wazuh.core.manager.status', return_value=manager_status) diff --git a/src/Makefile b/src/Makefile index b47f5d42be7..6b8d2058371 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2420,7 +2420,7 @@ ifneq (,$(filter ${OPTIMIZE_CPYTHON},YES yes y Y 1)) CPYTHON_FLAGS=--enable-optimizations endif -wpython: install_dependencies install_framework install_api install_mitre +wpython: install_dependencies install_framework install_api install_comms_api install_mitre build_python: ifeq (,${INSTALLDIR}) @@ -2458,6 +2458,9 @@ install_framework: install_python install_api: install_python cd ../api && ${WPYTHON_DIR}/bin/python3 -m pip install . --use-pep517 --prefix=${WPYTHON_DIR} && rm -rf build/ +install_comms_api: install_python + cd ../apis/comms_api && ${WPYTHON_DIR}/bin/python3 -m pip install . --use-pep517 --prefix=${WPYTHON_DIR} && rm -rf build/ + install_mitre: install_python cd ../tools/mitre && ${WPYTHON_DIR}/bin/python3 mitredb.py -d ${INSTALLDIR}/var/db/mitre.db diff --git a/src/init/inst-functions.sh b/src/init/inst-functions.sh index ca6fc7c9932..da56de01a57 100644 --- a/src/init/inst-functions.sh +++ b/src/init/inst-functions.sh @@ -1138,9 +1138,12 @@ InstallLocal() ${MAKEBIN} --quiet -C ../api backup INSTALLDIR=${INSTALLDIR} REVISION=${REVISION} fi - ### Install API + ### Install Server management API ${MAKEBIN} --quiet -C ../api install INSTALLDIR=${INSTALLDIR} + ### Install Agent comms API + ${MAKEBIN} --quiet -C ../apis/comms_api install INSTALLDIR=${INSTALLDIR} + ### Restore old API if [ "X${update_only}" = "Xyes" ]; then ${MAKEBIN} --quiet -C ../api restore INSTALLDIR=${INSTALLDIR} REVISION=${REVISION} diff --git a/tools/bump_version.sh b/tools/bump_version.sh index da048a5c2cc..e0e38e63138 100755 --- a/tools/bump_version.sh +++ b/tools/bump_version.sh @@ -32,6 +32,7 @@ FW_INIT="../framework/wazuh/__init__.py" CLUSTER_INIT="../framework/wazuh/core/cluster/__init__.py" API_SETUP="../api/setup.py" API_SPEC="../api/api/spec/spec.yaml" +COMMS_API_SETUP="../apis/comms_api/setup.py" VERSION_DOCU="../src/Doxyfile" WIN_RESOURCE="../src/win32/version.rc" @@ -180,6 +181,10 @@ then sed -E -i'' -e "s_/v[0-9]+\.[0-9]+\.[0-9]+_/${version}_g" $API_SPEC sed -E -i'' -e "s_com/[0-9]+\.[0-9]+_com/$(expr match "$version" 'v\([0-9]*.[0-9]*\).*')_g" $API_SPEC + # Agent comms API + + sed -E -i'' -e "s/version='.+',/version='${version:1}',/g" $COMMS_API_SETUP + # Documentation config file sed -E -i'' -e "s/PROJECT_NUMBER = \".+\"/PROJECT_NUMBER = \"$version\"/g" $VERSION_DOCU From 769106103b833cba8b6f4b5bd5ef4305bcbd1b39 Mon Sep 17 00:00:00 2001 From: GGP1 Date: Tue, 23 Jul 2024 12:45:28 -0300 Subject: [PATCH 3/8] Add dependencies and sub-dependencies --- framework/requirements.txt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/framework/requirements.txt b/framework/requirements.txt index e27233e53df..627541bd305 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -1,3 +1,4 @@ +annotated-types==0.7.0 anyio==4.1.0 aiohttp==3.9.5 aiosignal==1.3.1 @@ -22,11 +23,14 @@ content-size-limit-asgi==0.1.5 cryptography==42.0.4 Cython==0.29.36 defusedxml==0.6.0 +dnspython==2.6.1 docker==7.1.0 docker-pycreds==0.4.0 docutils==0.15.2 +email_validator==2.2.0 Events==0.5 exceptiongroup==1.2.0 +fastapi==0.111.1 frozenlist==1.2.0 future==0.18.3 google-api-core==1.30.0 @@ -40,6 +44,7 @@ google-resumable-media==1.3.1 greenlet==2.0.2 grpc-google-iam-v1==0.12.3 grpcio==1.58.0 +gunicorn==22.0.0 httpcore==1.0.2 httpx==0.26.0 h11==0.14.0 @@ -68,6 +73,8 @@ pathlib==1.0.1 protobuf==3.19.6 proto-plus==1.19.0 pyarrow==14.0.1 +pydantic==2.8.2 +pydantic_core==2.20.1 psutil==5.9.0 pyasn1==0.4.8 pyasn1-modules==0.2.8 @@ -102,9 +109,3 @@ xmltodict==0.12.0 yarl==1.7.0 zipp==3.3.2 content_size_limit_asgi==0.1.5 - -# Agent comms API -# TODO: define versions -fastapi -gunicorn -pydantic From 78822c24db0d7cb810ae861cef08302b1dd4b1af Mon Sep 17 00:00:00 2001 From: GGP1 Date: Tue, 23 Jul 2024 15:01:34 -0300 Subject: [PATCH 4/8] Add wazuh-comms-api logging configuration --- api/api/alogging.py | 8 +++++++- api/api/constants.py | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/api/api/alogging.py b/api/api/alogging.py index 70a4579d906..c4279813aad 100644 --- a/api/api/alogging.py +++ b/api/api/alogging.py @@ -183,7 +183,8 @@ def set_logging(log_filepath, log_level='INFO', foreground_mode=False) -> dict: }, "loggers": { "wazuh-api": {"handlers": hdls, "level": log_level, "propagate": False}, - "start-stop-api": {"handlers": hdls, "level": 'INFO', "propagate": False} + "start-stop-api": {"handlers": hdls, "level": 'INFO', "propagate": False}, + "wazuh-comms-api": {"handlers": hdls, "level": log_level, "propagate": False} } } @@ -209,6 +210,11 @@ def set_logging(log_filepath, log_level='INFO', foreground_mode=False) -> dict: log_config_dict['loggers']['uvicorn.error'] = {"handlers": hdls, "level": 'WARNING', "propagate": False} log_config_dict['loggers']['uvicorn.access'] = {'level': 'WARNING'} + # Configure the gunicorn loggers. They will be created by the gunicorn process. + log_config_dict['loggers']['gunicorn'] = {"handlers": hdls, "level": log_level, "propagate": False} + log_config_dict['loggers']['gunicorn.error'] = {"handlers": hdls, "level": log_level, "propagate": False} + log_config_dict['loggers']['gunicorn.access'] = {'level': log_level} + return log_config_dict diff --git a/api/api/constants.py b/api/api/constants.py index 0b3f18fce91..2b3e992ffd6 100644 --- a/api/api/constants.py +++ b/api/api/constants.py @@ -14,6 +14,7 @@ SECURITY_CONFIG_PATH = os.path.join(SECURITY_PATH, 'security.yaml') RELATIVE_SECURITY_PATH = os.path.relpath(SECURITY_PATH, common.WAZUH_PATH) API_LOG_PATH = os.path.join(common.WAZUH_PATH, 'logs', 'api') +COMMS_API_LOG_PATH = os.path.join(common.WAZUH_PATH, 'logs', 'comms_api') API_SSL_PATH = os.path.join(CONFIG_PATH, 'ssl') INSTALLATION_UID_PATH = os.path.join(SECURITY_PATH, 'installation_uid') INSTALLATION_UID_KEY = 'installation_uid' From 043cc6b829a9d9e21d0889388c6ac633f7100ee7 Mon Sep 17 00:00:00 2001 From: GGP1 Date: Wed, 24 Jul 2024 09:27:54 -0300 Subject: [PATCH 5/8] Add wazuh-comms-apid daemon --- src/init/wazuh-server.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/init/wazuh-server.sh b/src/init/wazuh-server.sh index 3d7f5b798e7..a61cf77c4c0 100755 --- a/src/init/wazuh-server.sh +++ b/src/init/wazuh-server.sh @@ -27,7 +27,7 @@ fi AUTHOR="Wazuh Inc." USE_JSON=false -DAEMONS="wazuh-clusterd wazuh-modulesd wazuh-monitord wazuh-logcollector wazuh-remoted wazuh-syscheckd wazuh-analysisd wazuh-maild wazuh-execd wazuh-db wazuh-authd wazuh-agentlessd wazuh-integratord wazuh-dbd wazuh-csyslogd wazuh-apid" +DAEMONS="wazuh-clusterd wazuh-modulesd wazuh-monitord wazuh-logcollector wazuh-remoted wazuh-syscheckd wazuh-analysisd wazuh-maild wazuh-execd wazuh-db wazuh-authd wazuh-agentlessd wazuh-integratord wazuh-dbd wazuh-csyslogd wazuh-apid wazuh-comms-apid" OP_DAEMONS="wazuh-clusterd wazuh-maild wazuh-agentlessd wazuh-integratord wazuh-dbd wazuh-csyslogd" DEPRECATED_DAEMONS="ossec-authd" From d2dceaef7e94e491885a7a0abd77015a69fb174a Mon Sep 17 00:00:00 2001 From: GGP1 Date: Wed, 24 Jul 2024 14:28:11 -0300 Subject: [PATCH 6/8] Move assign_wazuh_ownership() to utils --- api/scripts/wazuh_apid.py | 22 +++---------------- framework/wazuh/core/tests/test_utils.py | 28 ++++++++++++++++++++++++ framework/wazuh/core/utils.py | 15 +++++++++++++ 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/api/scripts/wazuh_apid.py b/api/scripts/wazuh_apid.py index 866b60c7471..8f7fc1120b9 100755 --- a/api/scripts/wazuh_apid.py +++ b/api/scripts/wazuh_apid.py @@ -49,22 +49,6 @@ def spawn_authentication_pool(): signal.signal(signal.SIGINT, signal.SIG_IGN) -def assign_wazuh_ownership(filepath: str): - """Create a file if it doesn't exist and assign ownership. - - Parameters - ---------- - filepath : str - File to assign ownership. - """ - if not os.path.isfile(filepath): - f = open(filepath, "w") - f.close() - if os.stat(filepath).st_gid != common.wazuh_gid() or \ - os.stat(filepath).st_uid != common.wazuh_uid(): - os.chown(filepath, common.wazuh_uid(), common.wazuh_gid()) - - def configure_ssl(params): """Configure https files and permission, and set the uvicorn dictionary configuration keys. @@ -106,8 +90,8 @@ def configure_ssl(params): logger.warning(SSL_DEPRECATED_MESSAGE.format(ssl_protocol=config_ssl_protocol)) # Check and assign ownership to wazuh user for server.key and server.crt files - assign_wazuh_ownership(api_conf['https']['key']) - assign_wazuh_ownership(api_conf['https']['cert']) + utils.assign_wazuh_ownership(api_conf['https']['key']) + utils.assign_wazuh_ownership(api_conf['https']['cert']) params['ssl_version'] = ssl.PROTOCOL_TLS_SERVER @@ -386,7 +370,7 @@ def error(self, msg, *args, **kws): # set permission on log files for handler in uvicorn_params['log_config']['handlers'].values(): if 'filename' in handler: - assign_wazuh_ownership(handler['filename']) + utils.assign_wazuh_ownership(handler['filename']) os.chmod(handler['filename'], 0o660) # Configure and create the wazuh-api logger diff --git a/framework/wazuh/core/tests/test_utils.py b/framework/wazuh/core/tests/test_utils.py index 52607bac313..ddb8c211749 100644 --- a/framework/wazuh/core/tests/test_utils.py +++ b/framework/wazuh/core/tests/test_utils.py @@ -160,6 +160,34 @@ def to_dict(self): ''' +@patch('os.chown') +@patch('wazuh.core.common.wazuh_uid') +@patch('wazuh.core.common.wazuh_gid') +def test_assign_wazuh_ownership(mock_gid, mock_uid, mock_chown): + """Test assign_wazuh_ownership function.""" + with TemporaryDirectory() as tmp_dirname: + tmp_file = NamedTemporaryFile(dir=tmp_dirname, delete=False) + filename = os.path.join(tmp_dirname, tmp_file.name) + utils.assign_wazuh_ownership(filename) + + mock_chown.assert_called_once_with(filename, mock_uid(), mock_gid()) + + +@patch('os.chown') +@patch('wazuh.core.common.wazuh_uid') +@patch('wazuh.core.common.wazuh_gid') +def test_assign_wazuh_ownership_write_file(mock_gid, mock_uid, mock_chown): + """Test assign_wazuh_ownership function with a non-regular file.""" + with TemporaryDirectory() as tmp_dirname: + tmp_file = NamedTemporaryFile(dir=tmp_dirname, delete=False) + filename = os.path.join(tmp_dirname, tmp_file.name) + + with patch('os.path.isfile', return_value=False): + with patch('builtins.open') as mock_open: + utils.assign_wazuh_ownership(filename) + mock_open.assert_called_once_with(filename, 'w') + + mock_chown.assert_called_once_with(filename, mock_uid(), mock_gid()) @pytest.mark.parametrize('month', [ 1, diff --git a/framework/wazuh/core/utils.py b/framework/wazuh/core/utils.py index 21ac2200461..de04b21cfa0 100755 --- a/framework/wazuh/core/utils.py +++ b/framework/wazuh/core/utils.py @@ -42,6 +42,21 @@ t_cache = TTLCache(maxsize=4500, ttl=60) +def assign_wazuh_ownership(filepath: str): + """Create a file if it doesn't exist and assign ownership. + + Parameters + ---------- + filepath : str + File to assign ownership. + """ + if not os.path.isfile(filepath): + f = open(filepath, "w") + f.close() + if os.stat(filepath).st_gid != common.wazuh_gid() or \ + os.stat(filepath).st_uid != common.wazuh_uid(): + os.chown(filepath, common.wazuh_uid(), common.wazuh_gid()) + def clean_pid_files(daemon: str) -> None: """Check the existence of '.pid' files for a specified daemon. From 34261ea1786a0f97bde21922916d9af2961da1a2 Mon Sep 17 00:00:00 2001 From: GGP1 Date: Fri, 26 Jul 2024 09:18:30 -0300 Subject: [PATCH 7/8] Add base log path --- api/api/constants.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/api/api/constants.py b/api/api/constants.py index 2b3e992ffd6..bfde572c6c9 100644 --- a/api/api/constants.py +++ b/api/api/constants.py @@ -13,8 +13,9 @@ SECURITY_PATH = os.path.join(CONFIG_PATH, 'security') SECURITY_CONFIG_PATH = os.path.join(SECURITY_PATH, 'security.yaml') RELATIVE_SECURITY_PATH = os.path.relpath(SECURITY_PATH, common.WAZUH_PATH) -API_LOG_PATH = os.path.join(common.WAZUH_PATH, 'logs', 'api') -COMMS_API_LOG_PATH = os.path.join(common.WAZUH_PATH, 'logs', 'comms_api') +BASE_LOG_PATH = os.path.join(common.WAZUH_PATH, 'logs') +API_LOG_PATH = os.path.join(BASE_LOG_PATH, 'api') +COMMS_API_LOG_PATH = os.path.join(BASE_LOG_PATH, 'comms_api') API_SSL_PATH = os.path.join(CONFIG_PATH, 'ssl') INSTALLATION_UID_PATH = os.path.join(SECURITY_PATH, 'installation_uid') INSTALLATION_UID_KEY = 'installation_uid' From f97fbe8048cd74ba7736970695a60a734ca58b61 Mon Sep 17 00:00:00 2001 From: GGP1 Date: Fri, 26 Jul 2024 09:18:43 -0300 Subject: [PATCH 8/8] Use custom exceptions --- framework/wazuh/core/exception.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/framework/wazuh/core/exception.py b/framework/wazuh/core/exception.py index 2510212dfcf..c4404cab6eb 100755 --- a/framework/wazuh/core/exception.py +++ b/framework/wazuh/core/exception.py @@ -441,6 +441,12 @@ class WazuhException(Exception): 2200: {'message': 'Could not connect to the indexer'}, 2201: {'message': 'Indexer credentials not provided'}, + # Agent comms API + 2700: {'message': 'Private key does not match with the certificate'}, + 2701: {'message': 'PEM phrase is not correct'}, + 2702: {'message': 'Ensure the certificates have the correct permissions'}, + 2703: {'message': 'Wazuh comms API SSL error. Please, ensure the configuration is correct'}, + # Cluster 3000: 'Cluster', 3001: 'Error creating zip file', @@ -808,6 +814,12 @@ class WazuhHAPHelperError(WazuhClusterError): _default_type = "about:blank" _default_title = "HAProxy Helper Error" +class WazuhCommsAPIError(WazuhInternalError): + """ + This type of exception is raised inside the Agent comms API. + """ + _default_type = "about:blank" + _default_title = "Agent comms API Error" class WazuhIndexerError(WazuhInternalError): """