diff options
Diffstat (limited to 'moon_engine/moon_engine/server.py')
-rw-r--r-- | moon_engine/moon_engine/server.py | 288 |
1 files changed, 288 insertions, 0 deletions
diff --git a/moon_engine/moon_engine/server.py b/moon_engine/moon_engine/server.py new file mode 100644 index 00000000..0b0f28b2 --- /dev/null +++ b/moon_engine/moon_engine/server.py @@ -0,0 +1,288 @@ +# Software Name: MOON + +# Version: 5.4 + +# SPDX-FileCopyrightText: Copyright (c) 2018-2020 Orange and its contributors +# SPDX-License-Identifier: Apache-2.0 + +# This software is distributed under the 'Apache License 2.0', +# the text of which is available at 'http://www.apache.org/licenses/LICENSE-2.0.txt' +# or see the "LICENSE" file for more details. + + + +from falcon.http_error import HTTPError +import hug +import logging.config +import json +import re +import requests +import sys +from uuid import uuid4 +from moon_engine.api import ERROR_CODE +from moon_engine.api import status, logs, import_json, configuration +from moon_utilities import exceptions +from moon_utilities import auth_functions +from moon_utilities import json_utils +from moon_cache import cache +from moon_engine import authz_driver + +LOGGER = logging.getLogger("moon.engine.server") +CACHE = None + + +@hug.directive() +def server_uuid(default="", **kwargs): + """ + Hug directive allowing to get the UUID of the component everywhere + :param default: + :param kwargs: + :return: UUID of the component + """ + return configuration.get_configuration("uuid") + + +def get_updates_from_manager(): + """ + Request the Manager to get all data from the database + :return: None + """ + LOGGER.info("Retrieving all data from Manager") + for attribute in ( + "pdp", + "models", + "policies", + "subjects", + "objects", + "actions", + "subject_categories", + "object_categories", + "action_categories", + "subject_assignments", + "object_assignments", + "action_assignments", + "meta_rules", + # "rules", + ): + # Note: force updates by getting attributes + LOGGER.info("Retrieving {} from manager {}".format( + attribute, configuration.get_configuration("manager_url"))) + getattr(CACHE, attribute) + + +def get_updates_from_local_conf(): + """ + Read the local data file and update the cache + :return: None + """ + filename = configuration.get_configuration("data") + LOGGER.info("Retrieving all data from configuration ({})".format(filename)) + data = json.load(open(filename)) + LOGGER.debug("keys={}".format(list(data.keys()))) + tool = json_utils.JsonImport(driver_name="cache", driver=CACHE) + tool.import_json(body=data) + + +def get_attributes_from_config(filename): + """ + Retrieve the configuration from the file given in the command line + :param filename: filename of the configuration file + :return: None + """ + # TODO: manage the case if the filename attribute doesn't contain a true filename + # => case if it doesn't start with Gunicorn + # => generate a temporary RAM file and point to the moon.yaml in the source code + for line in open(filename): + _match_conf = re.match(r"moon\s*=\s*\"(.+)\"", line) + if _match_conf: + yaml_filename = _match_conf.groups()[0] + configuration.CONF_FILE = yaml_filename + _conf = configuration.search_config_file(yaml_filename) + break + else: + LOGGER.warning("Cannot find Moon configuration filename in {}".format(filename)) + _conf = configuration.search_config_file("moon.yaml") + + configuration.set_configuration(_conf) + + +def get_bind_from_configfile(filename): + """ + Retrieve the binding configuration from the file given in the command line + :param filename: filename of the configuration file + :return: URL + """ + # TODO: manage the case if the filename attribute doesn't contain a true filename + # => case if it doesn't start with Gunicorn + # => case during tests + # => generate a temporary RAM file and point to the moon.yaml in the source code + for line in open(filename): + _match_conf = re.match(r"bind\s*=\s*\"(.+)\"", line) + if _match_conf: + return "http://" + _match_conf.groups()[0].replace("0.0.0.0", "127.0.0.1") # nosec + else: + LOGGER.warning("Cannot find binding configuration in {}".format(filename)) + + +def init_logging_system(): + """ + Initialize the logging system + either by the configuration given in the configuration file + either by the configuration in the Manager + :return: None + """ + logging_conf = configuration.get_configuration("logging") + manager_url = configuration.get_configuration("manager_url") + if logging_conf: + configuration.init_logging() + elif manager_url: + req = requests.get("{}/config".format(manager_url)) + if req.status_code != 200: + raise Exception("Error getting configurationĀ data " + "from manager (code={})".format(req.status_code)) + logging.config.dictConfig(req.json().get("logging", {})) + + +def get_policy_configuration_from_manager(): + """ + Retrieve all data from the Manager + :return: None + """ + pdp_id = CACHE.get_pdp_from_vim_project(configuration.get_configuration("uuid")) + CACHE.update(pdp_id=pdp_id) + + +def init_pipeline(): + """ + Initialize the pipeline configuration + :return: None + """ + if configuration.get_configuration("management").get("url"): + get_policy_configuration_from_manager() + + +def initialize(): + """Adds initial data to the api on startup""" + global CACHE + LOGGER.warning("Starting the server and initializing data") + filename = sys.argv[-1] + try: + get_attributes_from_config(filename) + except FileNotFoundError: + LOGGER.warning("{} file not found".format(filename)) + except IsADirectoryError: + LOGGER.warning("{} file is a directory.".format(filename)) + + init_logging_system() + + LOGGER.info("management={}".format(configuration.get_configuration("management"))) + auth_functions.init_db(configuration.get_configuration("management").get("token_file")) + CACHE = cache.Cache.getInstance( + manager_url=configuration.get_configuration("management").get('url'), + incremental=configuration.get_configuration("incremental_updates"), + manager_api_key=configuration.get_configuration("api_token")) + + if configuration.get_configuration("type") == "pipeline": + init_pipeline() + + if not configuration.get_configuration("incremental_updates"): + if configuration.get_configuration("manager_url"): + get_updates_from_manager() + elif configuration.get_configuration("data"): + get_updates_from_local_conf() + auth_functions.add_user("admin", uuid4().hex) + # NOTE: the password is not saved anywhere but + # the API key is printed in the log + # and is xor-ed with the Manager API key + api_key = auth_functions.get_api_key_for_user("admin") + LOGGER.info(f"api_key={api_key}") + LOGGER.info(f"configuration.get_configuration('api_token')={configuration.get_configuration('api_token')}") + try: + encrypt_key = auth_functions.xor_encode(api_key, + configuration.get_configuration("api_token")) + except exceptions.EncryptError: + encrypt_key = "" + try: + local_server = get_bind_from_configfile(filename) + CACHE.set_current_server(url=local_server, api_key=api_key) + except (FileNotFoundError, IsADirectoryError): + LOGGER.warning("Cannot find configuration file {}".format(filename)) + LOGGER.critical("APIKEY={}".format(encrypt_key)) + authz_driver.init() + + +def __get_status_code(exception): + """ + Return the status code to send depending on the exception thrown + :param exception: the exception that will be sent + :return: + """ + if isinstance(exception, HTTPError): + return exception.status + status_code = getattr(exception, "code", 500) + if status_code in ERROR_CODE: + status_code = ERROR_CODE[status_code] + else: + status_code = hug.HTTP_500 + return status_code + + +@hug.exception(exceptions.MoonError) +def handle_custom_exceptions(exception, response): + """ + Handle Moon exceptions + :param exception: the exception that has been raised + :param response: the response to send to the client + :return: JSON data to send to the client + """ + response.status = __get_status_code(exception) + error_message = {"result": False, + 'message': str(exception), + "code": getattr(exception, "code", 500)} + LOGGER.exception(exception) + return error_message + + +@hug.exception(Exception) +def handle_exception(exception, response): + """ + Handle general exceptions + :param exception: the exception that has been raised + :param response: the response to send to the client + :return: JSON data to send to the client + """ + response.status = __get_status_code(exception) + LOGGER.exception(exception) + return {"result": False, 'message': str(exception), "code": getattr(exception, "code", 500)} + + +def get_api_from_plugins(api_type): + return configuration.get_plugins_by_type(api_type) + + +@hug.extend_api() +def with_other_apis(): + """ + Give to Hug all available APIs + :return: list of APIs + """ + initialize() + _type = configuration.get_configuration("type") + if _type == "wrapper": + from moon_engine.api.wrapper.api import pipeline + from moon_engine.api.wrapper.api import update as wrapper_update + from moon_engine.api.wrapper.api import authz as wrapper_authz + LOGGER.info("Starting the Wrapper API interfaces") + return [status, logs, import_json, wrapper_update, pipeline, wrapper_authz] + \ + list(configuration.get_plugins_by_type("wrapper_api")) + elif _type == "pipeline": + from moon_engine.api.pipeline import update as pipeline_update + from moon_engine.api.pipeline import authz as pipeline_authz + LOGGER.info("Starting the Pipeline API interfaces") + return [status, logs, import_json, pipeline_update, pipeline_authz] + \ + list(configuration.get_plugins_by_type("engine_api")) + raise Exception("The type of component must be 'wrapper' or 'pipeline' (got {} instead)".format( + _type + )) + + |