def start(foreground, root, config_file): """Run the Wazuh API. If another Wazuh API is running, this function fails. This function exits with 0 if successful or 1 if failed because the API was already running. Arguments --------- foreground : bool If the API must be daemonized or not root : bool If true, the daemon is run as root. Normally not recommended for security reasons config_file : str Path to the API config file """ import asyncio import logging import os import ssl import connexion import uvloop from aiohttp_cache import setup_cache from api import alogging, configuration import wazuh.security from api import __path__ as api_path # noinspection PyUnresolvedReferences from api import validator from api.api_exception import APIError from api.constants import CONFIG_FILE_PATH from api.middlewares import set_user_name, security_middleware, response_postprocessing, request_logging, \ set_secure_headers from api.signals import modify_response_headers from api.uri_parser import APIUriParser from api.util import to_relative_path from wazuh.core import pyDaemonModule def set_logging(log_path='logs/api.log', foreground_mode=False, debug_mode='info'): for logger_name in ('connexion.aiohttp_app', 'connexion.apis.aiohttp_api', 'wazuh-api'): api_logger = alogging.APILogger( log_path=log_path, foreground_mode=foreground_mode, logger_name=logger_name, debug_level='info' if logger_name != 'wazuh-api' and debug_mode != 'debug2' else debug_mode) api_logger.setup_logger() if config_file is not None: configuration.api_conf.update( configuration.read_yaml_config(config_file=config_file)) api_conf = configuration.api_conf security_conf = configuration.security_conf log_path = api_conf['logs']['path'] # Set up logger set_logging(log_path=log_path, debug_mode=api_conf['logs']['level'], foreground_mode=foreground) logger = logging.getLogger('wazuh-api') # `use_only_authd` deprecated on v4.3.0. To be removed if "use_only_authd" in api_conf: del api_conf["use_only_authd"] logger.warning( "'use_only_authd' option was deprecated on v4.3.0. Wazuh Authd will always be used" ) # Set correct permissions on api.log file if os.path.exists(os.path.join(common.wazuh_path, log_path)): os.chown(os.path.join(common.wazuh_path, log_path), common.wazuh_uid(), common.wazuh_gid()) os.chmod(os.path.join(common.wazuh_path, log_path), 0o660) # Configure https ssl_context = None if api_conf['https']['enabled']: try: # Generate SSL if it does not exist and HTTPS is enabled if not os.path.exists( api_conf['https']['key']) or not os.path.exists( api_conf['https']['cert']): logger.info( 'HTTPS is enabled but cannot find the private key and/or certificate. ' 'Attempting to generate them') private_key = configuration.generate_private_key( api_conf['https']['key']) logger.info( f"Generated private key file in WAZUH_PATH/{to_relative_path(api_conf['https']['key'])}" ) configuration.generate_self_signed_certificate( private_key, api_conf['https']['cert']) logger.info( f"Generated certificate file in WAZUH_PATH/{to_relative_path(api_conf['https']['cert'])}" ) # Load SSL context allowed_ssl_protocols = { 'tls': ssl.PROTOCOL_TLS, 'tlsv1': ssl.PROTOCOL_TLSv1, 'tlsv1.1': ssl.PROTOCOL_TLSv1_1, 'tlsv1.2': ssl.PROTOCOL_TLSv1_2 } ssl_protocol = allowed_ssl_protocols[api_conf['https'] ['ssl_protocol'].lower()] ssl_context = ssl.SSLContext(protocol=ssl_protocol) if api_conf['https']['use_ca']: ssl_context.verify_mode = ssl.CERT_REQUIRED ssl_context.load_verify_locations(api_conf['https']['ca']) ssl_context.load_cert_chain(certfile=api_conf['https']['cert'], keyfile=api_conf['https']['key']) # Load SSL ciphers if any has been specified if api_conf['https']['ssl_ciphers']: ssl_ciphers = api_conf['https']['ssl_ciphers'].upper() try: ssl_context.set_ciphers(ssl_ciphers) except ssl.SSLError: logger.error( str( APIError( 2003, details='SSL ciphers cannot be selected'))) sys.exit(1) except ssl.SSLError: logger.error( str( APIError( 2003, details= 'Private key does not match with the certificate'))) sys.exit(1) except IOError as e: if e.errno == 22: logger.error( str(APIError(2003, details='PEM phrase is not correct'))) elif e.errno == 13: logger.error( str( APIError( 2003, details= 'Ensure the certificates have the correct permissions' ))) else: print( 'Wazuh API SSL ERROR. Please, ensure if path to certificates is correct in the configuration ' f'file WAZUH_PATH/{to_relative_path(CONFIG_FILE_PATH)}') sys.exit(1) # Drop privileges to wazuh if not root: if api_conf['drop_privileges']: os.setgid(common.wazuh_gid()) os.setuid(common.wazuh_uid()) else: print(f"Starting API as root") # Foreground/Daemon utils.check_pid('wazuh-apid') if not foreground: pyDaemonModule.pyDaemon() pid = os.getpid() pyDaemonModule.create_pid('wazuh-apid', pid) or register( pyDaemonModule.delete_pid, 'wazuh-apid', pid) if foreground: print(f"Starting API in foreground (pid: {pid})") # Load the SPEC file into memory to use as a reference for future calls wazuh.security.load_spec() # Set up API asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) app = connexion.AioHttpApp(__name__, host=api_conf['host'], port=api_conf['port'], specification_dir=os.path.join( api_path[0], 'spec'), options={ "swagger_ui": False, 'uri_parser_class': APIUriParser }, only_one_api=True) app.add_api('spec.yaml', arguments={ 'title': 'Wazuh API', 'protocol': 'https' if api_conf['https']['enabled'] else 'http', 'host': api_conf['host'], 'port': api_conf['port'] }, strict_validation=True, validate_responses=False, pass_context_arg_name='request', options={ "middlewares": [ response_postprocessing, set_user_name, security_middleware, request_logging, set_secure_headers ] }) # Maximum body size that the API can accept (bytes) app.app._client_max_size = configuration.api_conf['max_upload_size'] # Enable CORS if api_conf['cors']['enabled']: import aiohttp_cors cors = aiohttp_cors.setup( app.app, defaults={ api_conf['cors']['source_route']: aiohttp_cors.ResourceOptions( expose_headers=api_conf['cors']['expose_headers'], allow_headers=api_conf['cors']['allow_headers'], allow_credentials=api_conf['cors']['allow_credentials']) }) # Configure CORS on all endpoints. for route in list(app.app.router.routes()): cors.add(route) # Enable cache plugin if api_conf['cache']['enabled']: setup_cache(app.app) # Add application signals app.app.on_response_prepare.append(modify_response_headers) # API configuration logging logger.debug(f'Loaded API configuration: {api_conf}') logger.debug(f'Loaded security API configuration: {security_conf}') # Start API app.run(port=api_conf['port'], host=api_conf['host'], ssl_context=ssl_context, access_log_class=alogging.AccessLogger, use_default_access_log=True)
main_logger.error("Cluster is already running.") sys.exit(1) # clean wazuh.core.cluster.cluster.clean_up() # Foreground/Daemon if not args.foreground: pyDaemonModule.pyDaemon() # Drop privileges to wazuh if not args.root: os.setgid(common.wazuh_gid()) os.setuid(common.wazuh_uid()) check_pid('wazuh-clusterd') pid = os.getpid() pyDaemonModule.create_pid('wazuh-clusterd', pid) if args.foreground: print(f"Starting cluster in foreground (pid: {pid})") original_sig_handler = signal(SIGTERM, exit_handler) main_function = master_main if cluster_configuration[ 'node_type'] == 'master' else worker_main try: asyncio.run( main_function(args, cluster_configuration, cluster_items, main_logger)) except KeyboardInterrupt: main_logger.info("SIGINT received. Bye!")