def read_yaml_config(config_file=common.api_config_path, default_conf=None) -> Dict: """Reads user API configuration and merges it with the default one :return: API configuration """ if default_conf is None: default_conf = default_api_configuration if os.path.exists(config_file): try: with open(config_file) as f: configuration = yaml.safe_load(f) except IOError as e: raise APIError(2004, details=e.strerror) else: configuration = None # If any value is missing from user's cluster configuration, add the default one: if configuration is None: configuration = copy.deepcopy(default_conf) else: dict_to_lowercase(configuration) configuration = fill_dict(default_conf, configuration) # Append ossec_path to all paths in configuration append_ossec_path(configuration, [('logs', 'path'), ('https', 'key'), ('https', 'cert'), ('https', 'ca')]) return configuration
def fill_dict(default: Dict, config: Dict, json_schema: Dict) -> Dict: """Validate and fill a dictionary's missing values using default ones. Parameters ---------- default : dict Dictionary with default values. config : dict Dictionary to be filled. json_schema : dict Jsonschema with allowed properties. Returns ------- dict Filled dictionary. """ try: validate(instance=config, schema=json_schema) except ValidationError as e: raise APIError(2000, details=e.message) for k, val in filter(lambda x: isinstance(x[1], dict), config.items()): for item, value in config[k].items(): config[k][item] = default[k][item] if value == "" else config[k][item] config[k] = {**default[k], **config[k]} return {**default, **config}
async def check_experimental(request, handler): if 'experimental' in request.path: if not configuration.api_conf['experimental_features']: raise_if_exc(APIError(code=2008)) response = await handler(request) return response
def wrapper(request): def coerce_dict(md): """ MultiDict -> dict of lists """ try: return md.to_dict(flat=False) except AttributeError: return dict(md.items()) # Raise exception if semicolon is used in q parameter if 'q' in request.query.keys(): q = parse_api_param(request.url, 'q') if q: if ';' in q: raise_if_exc(APIError(code=2009)) # Transform to lowercase the values for query parameter's spec.yaml enums lower_fields = ['component', 'configuration', 'hash', 'requirement', 'status', 'type', 'section', 'tag', 'level', 'resource'] request.query.update( {k.lower(): [list_item.lower() for list_item in v] if isinstance(v, list) else v.lower() for k, v in request.query.items() if k in lower_fields}) query = coerce_dict(request.query) path_params = coerce_dict(request.path_params) form = coerce_dict(request.form) request.query = self.resolve_query(query) request.path_params = self.resolve_path(path_params) request.form = self.resolve_form(form) response = function(request) return response
def wrapper(request): def coerce_dict(md): """ MultiDict -> dict of lists """ try: return md.to_dict(flat=False) except AttributeError: return dict(md.items()) # Raise exception if semicolon is used in q parameter if 'q' in request.query.keys(): q = parse_api_param(request.url, 'q') if q: if ';' in q: raise_if_exc(APIError(code=2009)) query = coerce_dict(request.query) path_params = coerce_dict(request.path_params) form = coerce_dict(request.form) request.query = self.resolve_query(query) request.path_params = self.resolve_path(path_params) request.form = self.resolve_form(form) response = function(request) return response
def read_yaml_config(config_file=CONFIG_FILE_PATH, default_conf=None) -> Dict: """Reads user API configuration and merges it with the default one :return: API configuration """ def replace_bools(conf): """Replace 'yes' and 'no' strings in configuration for actual booleans. Parameters ---------- conf : dict Current API configuration. """ for k in conf.keys(): if isinstance(conf[k], dict): replace_bools(conf[k]) else: if isinstance(conf[k], str): if conf[k].lower() == 'yes': conf[k] = True elif conf[k].lower() == 'no': conf[k] = False if default_conf is None: default_conf = default_api_configuration if config_file and os.path.exists(config_file): try: with open(config_file) as f: configuration = yaml.safe_load(f) # Replace strings for booleans configuration and replace_bools(configuration) except IOError as e: raise APIError(2004, details=e.strerror) else: configuration = None if configuration is None: configuration = copy.deepcopy(default_conf) else: # If any value is missing from user's configuration, add the default one: dict_to_lowercase(configuration) schema = security_config_schema if config_file == SECURITY_CONFIG_PATH else api_config_schema configuration = fill_dict(default_conf, configuration, schema) # Append Wazuh prefixes to all relative paths in configuration append_wazuh_prefixes( configuration, {API_SSL_PATH: [('https', 'key'), ('https', 'cert'), ('https', 'ca')]}) return configuration
def read_yaml_config(config_file=common.api_config_path, default_conf=None) -> Dict: """Reads user API configuration and merges it with the default one :return: API configuration """ def replace_bools(conf): """Replace 'yes' and 'no' strings in configuration for actual booleans. Parameters ---------- conf : dict Current API configuration. """ for k in conf.keys(): if isinstance(conf[k], dict): replace_bools(conf[k]) else: if isinstance(conf[k], str): if conf[k].lower() == 'yes': conf[k] = True elif conf[k].lower() == 'no': conf[k] = False if default_conf is None: default_conf = default_api_configuration if os.path.exists(config_file): try: with open(config_file) as f: configuration = yaml.safe_load(f) # Replace strings for booleans configuration and replace_bools(configuration) except IOError as e: raise APIError(2004, details=e.strerror) else: configuration = None # If any value is missing from user's cluster configuration, add the default one: if configuration is None: configuration = copy.deepcopy(default_conf) else: dict_to_lowercase(configuration) configuration = fill_dict(default_conf, configuration) # Append ossec_path to all paths in configuration append_ossec_path(configuration, [('logs', 'path'), ('https', 'key'), ('https', 'cert'), ('https', 'ca')]) return configuration
async def update_policy(request, policy_id: int, pretty: bool = False, wait_for_complete: bool = False): """Update the information of one specified policy. Parameters ---------- request : connexion.request policy_id : int Specific policy id in the system to be updated pretty : bool, optional Show results in human-readable format wait_for_complete : bool, optional Disable timeout response Returns ------- Policy information updated """ # get body parameters policy_added_model = dict() try: policy_added_model = await request.json() except JSONDecodeError as e: raise_if_exc(APIError(code=2005, details=e.msg)) f_kwargs = { 'policy_id': policy_id, 'name': policy_added_model.get('name', None), 'policy': policy_added_model.get('policy', None) } dapi = DistributedAPI( f=security.update_policy, f_kwargs=remove_nones_to_dict(f_kwargs), request_type='local_master', is_async=False, wait_for_complete=wait_for_complete, logger=logger, rbac_permissions=request['token_info']['rbac_policies']) data = raise_if_exc(await dapi.distribute_function()) return web.json_response(data=data, status=200, dumps=prettify if pretty else dumps)
def fill_dict(default: Dict, config: Dict) -> Dict: """Fills a dictionary's missing values using default ones. :param default: Dictionary with default values :param config: Dictionary to fill :return: Filled dictionary """ # Check there aren't extra configuration values in user's configuration: for k in config.keys(): if k not in default.keys(): raise APIError(2000, details=', '.join(config.keys() - default.keys())) for k, val in filter(lambda x: isinstance(x[1], dict), config.items()): for item, value in config[k].items(): config[k][item] = default[k][item] if value == "" else config[k][item] config[k] = {**default[k], **config[k]} return {**default, **config}
async def add_role(request, pretty: bool = False, wait_for_complete: bool = False): """Add one specified role. Parameters ---------- request : request.connexion pretty : bool, optional Show results in human-readable format wait_for_complete : bool, optional Disable timeout response Returns ------- Role information """ # get body parameters role_added_model = dict() try: role_added_model = await request.json() except JSONDecodeError as e: raise_if_exc(APIError(code=2005, details=e.msg)) f_kwargs = { 'name': role_added_model['name'], 'rule': role_added_model['rule'] } dapi = DistributedAPI( f=security.add_role, f_kwargs=remove_nones_to_dict(f_kwargs), request_type='local_master', is_async=False, wait_for_complete=wait_for_complete, logger=logger, rbac_permissions=request['token_info']['rbac_policies']) data = raise_if_exc(await dapi.distribute_function()) return web.json_response(data=data, status=200, dumps=prettify if pretty else dumps)
async def get_kwargs(cls, request, additional_kwargs: dict = None): try: dikt = request if isinstance(request, dict) else await request.json() f_kwargs = util.deserialize_model(dikt, cls).to_dict() except JSONDecodeError as e: raise_if_exc(APIError(code=2005, details=e.msg)) invalid = { key for key in dikt.keys() if key not in list(f_kwargs.keys()) } if invalid: raise_if_exc( WazuhError( 5005, extra_message='Invalid field found {}'.format(invalid))) if additional_kwargs is not None: f_kwargs.update(additional_kwargs) return f_kwargs
async def put_api_config(request, pretty=False, wait_for_complete=False): """Update current API configuration with the given one. :param pretty: Show results in human-readable format :param wait_for_complete: Disable timeout response """ try: f_kwargs = {"updated_config": await request.json()} except JSONDecodeError as e: raise_if_exc(APIError(code=2005, details=e.msg)) dapi = DistributedAPI( f=manager.update_api_config, f_kwargs=remove_nones_to_dict(f_kwargs), request_type='local_any', is_async=False, wait_for_complete=wait_for_complete, logger=logger, rbac_permissions=request['token_info']['rbac_policies']) data = raise_if_exc(await dapi.distribute_function()) return web.json_response(data=data, status=200, dumps=prettify if pretty else dumps)
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 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.uri_parser import APIUriParser from api.util import to_relative_path from wazuh.core import pyDaemonModule 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') # 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_ciphers = { 'tls': ssl.PROTOCOL_TLS, 'tlsv1': ssl.PROTOCOL_TLSv1, 'tlsv1.1': ssl.PROTOCOL_TLSv1_1, 'tlsv1.2': ssl.PROTOCOL_TLSv1_2 } try: ssl_cipher = allowed_ssl_ciphers[api_conf['https'] ['ssl_cipher'].lower()] except (KeyError, AttributeError): # KeyError: invalid string value # AttributeError: invalid boolean value logger.error( str( APIError( 2003, details='SSL cipher is not valid. Allowed values: ' 'TLS, TLSv1, TLSv1.1, TLSv1.2'))) sys.exit(1) ssl_context = ssl.SSLContext(protocol=ssl_cipher) 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']) 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 if not foreground: pyDaemonModule.pyDaemon() pyDaemonModule.create_pid('wazuh-apid', os.getpid()) else: print(f"Starting API in foreground") # 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 ] }) # 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) # 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)
except IOError as e: raise APIError(2004, details=e.strerror) else: configuration = None if configuration is None: configuration = copy.deepcopy(default_conf) else: # If any value is missing from user's configuration, add the default one: dict_to_lowercase(configuration) schema = security_config_schema if config_file == SECURITY_CONFIG_PATH else api_config_schema configuration = fill_dict(default_conf, configuration, schema) # Append wazuh_path to all paths in configuration append_wazuh_path(configuration, [('logs', 'path'), ('https', 'key'), ('https', 'cert'), ('https', 'ca')]) return configuration # Check if the default configuration is valid according to its jsonschema, so we are forced to update the schema if any # change is performed to the configuration. try: validate(instance=default_security_configuration, schema=security_config_schema) validate(instance=default_api_configuration, schema=api_config_schema) except ValidationError as e: raise APIError(2000, details=e.message) # Configuration - global object api_conf = dict() security_conf = read_yaml_config(config_file=SECURITY_CONFIG_PATH, default_conf=default_security_configuration)
def start(foreground: bool, root: bool, config_file: str): """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 logging import os from api import alogging, configuration from wazuh.core import pyDaemonModule, common, utils 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 # Set up logger set_logging(log_path=API_LOG_FILE_PATH, debug_mode=api_conf['logs']['level'], foreground_mode=foreground) logger = logging.getLogger('wazuh-api') import asyncio import ssl import connexion import uvloop from aiohttp_cache import setup_cache 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.rbac.orm import create_rbac_db # Check deprecated options. To delete after expected versions 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" ) if 'path' in api_conf['logs']: del api_conf['logs']['path'] logger.warning( "Log 'path' option was deprecated on v4.3.0. Default path will always be used: " f"{API_LOG_FILE_PATH}") # Set correct permissions on api.log file if os.path.exists(os.path.join(common.wazuh_path, API_LOG_FILE_PATH)): os.chown(os.path.join(common.wazuh_path, API_LOG_FILE_PATH), common.wazuh_uid(), common.wazuh_gid()) os.chmod(os.path.join(common.wazuh_path, API_LOG_FILE_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: error = APIError(2003, details='SSL ciphers cannot be selected') logger.error(error) raise error except ssl.SSLError: error = APIError( 2003, details='Private key does not match with the certificate') logger.error(error) raise error except IOError as exc: if exc.errno == 22: error = APIError(2003, details='PEM phrase is not correct') logger.error(error) raise error elif exc.errno == 13: error = APIError( 2003, details= 'Ensure the certificates have the correct permissions') logger.error(error) raise error else: msg = f'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)}' print(msg) logger.error(msg) raise exc utils.check_pid('wazuh-apid') # Drop privileges to ossec 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 if not foreground: pyDaemonModule.pyDaemon() pid = os.getpid() pyDaemonModule.create_pid('wazuh-apid', pid) or register( pyDaemonModule.delete_pid, 'wazuh-apid', pid) else: print(f"Starting API in foreground") create_rbac_db() # Spawn child processes with their own needed imports if 'thread_pool' not in common.mp_pools.get(): loop = asyncio.get_event_loop() loop.run_until_complete( asyncio.wait([ loop.run_in_executor( pool, getattr(sys.modules[__name__], f'spawn_{name}')) for name, pool in common.mp_pools.get().items() ])) # 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 try: app.run(port=api_conf['port'], host=api_conf['host'], ssl_context=ssl_context, access_log_class=alogging.AccessLogger, use_default_access_log=True) except OSError as exc: if exc.errno == 98: error = APIError(2010) logger.error(error) raise error else: logger.error(exc) raise exc
def start(foreground, root, config_file): """ Run the Wazuh API. If another Wazuh API is running, this function fails. The `stop` command should be used first. This function exits with 0 if success or 2 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 """ configuration.api_conf.update(configuration.read_yaml_config(config_file=args.config_file)) api_conf = configuration.api_conf cors = api_conf['cors'] log_path = api_conf['logs']['path'] ssl_context = None if api_conf['https']['enabled'] and os.path.exists(api_conf['https']['key']) and \ os.path.exists(api_conf['https']['cert']): try: ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS) 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']) except ssl.SSLError as e: raise APIError(2003, details='Private key does not match with the certificate') except OSError as e: if e.errno == 22: raise APIError(2003, details='PEM phrase is not correct') # Drop privileges to ossec if not root: if api_conf['drop_privileges']: os.setgid(common.ossec_gid()) os.setuid(common.ossec_uid()) # Foreground/Daemon if not foreground: print(f"Starting API in background") pyDaemonModule.pyDaemon() pyDaemonModule.create_pid('wazuh-apid', os.getpid()) set_logging(log_path=log_path, debug_mode=api_conf['logs']['level'], foreground_mode=args.foreground) # set correct permissions on api.log file if os.path.exists(os.path.join(common.ossec_path, log_path)): os.chown(os.path.join(common.ossec_path, log_path), common.ossec_uid(), common.ossec_gid()) os.chmod(os.path.join(common.ossec_path, log_path), 0o660) # Load the SPEC file into memory to use as a reference for future calls wazuh.security.load_spec() 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=True, pass_context_arg_name='request', options={"middlewares": [response_postprocessing, set_user_name, security_middleware]}) # Enable CORS if cors['enabled']: cors = aiohttp_cors.setup(app.app, defaults={ cors['source_route']: aiohttp_cors.ResourceOptions( expose_headers=cors['expose_headers'], allow_headers=cors['allow_headers'], allow_credentials=cors['allow_credentials'] ) }) # Configure CORS on all endpoints. for route in list(app.app.router.routes()): cors.add(route) # Enable cache plugin setup_cache(app.app) # Enable swagger UI plugin setup_swagger(app.app, ui_version=3, swagger_url='/ui', swagger_from_file=os.path.join(app.specification_dir, 'spec.yaml')) # Configure https if api_conf['https']['enabled']: # Generate SSC 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 = logging.getLogger('wazuh') logger.info('HTTPS is enabled but cannot find the private key and/or certificate. ' 'Attempting to generate them.') private_key = generate_private_key(api_conf['https']['key']) logger.info(f"Generated private key file in WAZUH_PATH/{to_relative_path(api_conf['https']['key'])}.") 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'])}.") if ssl_context is None: try: ssl_context = ssl.SSLContext(protocol=ssl.PROTOCOL_TLS) 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']) except ssl.SSLError: raise APIError(2003, details='Private key does not match with the certificate') except IOError: raise APIError(2003, details='Please, ensure if path to certificates is correct in the configuration ' f'file WAZUH_PATH/{to_relative_path(CONFIG_FILE_PATH)}') app.run(port=api_conf['port'], host=api_conf['host'], ssl_context=ssl_context, access_log_class=alogging.AccessLogger, use_default_access_log=True )