diff options
Diffstat (limited to 'moon_engine/moon_engine')
33 files changed, 2919 insertions, 0 deletions
diff --git a/moon_engine/moon_engine/__init__.py b/moon_engine/moon_engine/__init__.py new file mode 100644 index 00000000..0c44540e --- /dev/null +++ b/moon_engine/moon_engine/__init__.py @@ -0,0 +1,30 @@ +# 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. + + + +__version__ = "0.8" + + +def get_api_key(url, user, password): + import requests + from requests.auth import HTTPBasicAuth + _url = url + "/auth" + req = requests.get(_url, auth=HTTPBasicAuth(user, password)) + if req.status_code != 200: + raise Exception("Cannot authenticate on {} with {}".format(_url, user)) + return req.content.decode("utf-8").strip('"') + + +def serve(hostname="127.0.0.1", port=8080): + import hug + import moon_engine.server + hug.API(moon_engine.server).http.serve(host=hostname, port=port, display_intro=False) diff --git a/moon_engine/moon_engine/__main__.py b/moon_engine/moon_engine/__main__.py new file mode 100644 index 00000000..16f1d322 --- /dev/null +++ b/moon_engine/moon_engine/__main__.py @@ -0,0 +1,113 @@ +# 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. + + +import logging +import subprocess # nosec +import os +import sys +import hug.interface +from moon_engine.api import configuration + + +LOGGER = logging.getLogger("moon.engine") + + +@hug.cli("start_server") +@hug.local() +def start_server(conf_file): + """ Start the server of the engine """ + + try: + guni_conf_file = get_info(conf_file, "moon").strip('"\n') + port = get_info(conf_file, "bind").split(":")[1].strip('"\n') + log_dir = get_info(conf_file, "pid_file_dir").strip('"\n') + except ValueError: + return + + configuration.init_logging(guni_conf_file) + LOGGER.setLevel(logging.ERROR) + + pid_filename = log_dir + port + ".pid" + _command = ["gunicorn", "moon_engine.server:__hug_wsgi__", "-D", "-p", pid_filename, "-c", conf_file] + subprocess.Popen(_command, stdout=subprocess.PIPE, close_fds=True) # nosec + + +@hug.cli("stop_server") +@hug.local() +def stop_server(conf_file): + """ Stop the server of the engine """ + + try: + guni_conf_file = get_info(conf_file, "moon").strip('"\n') + port = get_info(conf_file, "bind").split(":")[1].strip('"\n') + log_dir = get_info(conf_file, "pid_file_dir").strip('"\n') + except ValueError: + return + + configuration.init_logging(guni_conf_file) + LOGGER.setLevel(logging.ERROR) + + pid_filename = log_dir + port + ".pid" + + try: + pid_file = open(pid_filename, 'r') + except FileNotFoundError: + LOGGER.error(f"File {pid_filename} not found. Server on port {port} not running?") + return + + try: + pid = int(pid_file.read()) + except ValueError: + LOGGER.error(f"The pid found in {pid_filename} is not valid") + return + + os.kill(pid, 15) + + +def get_info(conf, key): + with open(conf) as config: + lines = config.readlines() + for line in lines: + if line.startswith(key): + return line.split("=")[1].strip() + LOGGER.error(f"Key \"{key}\" missing from Gunicorn configuration file") + raise ValueError + + +def run(): + if len(sys.argv) > 1: + + command = sys.argv[1] + sys.argv.pop(1) + # if command == "conf": + # configuration.get_configuration.interface.cli() + # elif command == "db": + # configuration.init_database.interface.cli() + if command == "start": + start_server.interface.cli() + elif command == "stop": + stop_server.interface.cli() + else: + LOGGER.critical("Unknown command {}".format(command)) + + else: + # TODO: update the command management by using argparse + print("""Possible commands are: + # - conf + # - db + - start + - stop + """) + + +if __name__ == "__main__": + run() diff --git a/moon_engine/moon_engine/api/__init__.py b/moon_engine/moon_engine/api/__init__.py new file mode 100644 index 00000000..deafe11b --- /dev/null +++ b/moon_engine/moon_engine/api/__init__.py @@ -0,0 +1,29 @@ +# 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 import HTTP_400, HTTP_401, HTTP_402, HTTP_403, HTTP_404, HTTP_405, \ + HTTP_406, HTTP_407, HTTP_408, HTTP_409, HTTP_500 + +ERROR_CODE = { + 400: HTTP_400, + 401: HTTP_401, + 402: HTTP_402, + 403: HTTP_403, + 404: HTTP_404, + 405: HTTP_405, + 406: HTTP_406, + 407: HTTP_407, + 408: HTTP_408, + 409: HTTP_409, + 500: HTTP_500 +} diff --git a/moon_engine/moon_engine/api/authz/__init__.py b/moon_engine/moon_engine/api/authz/__init__.py new file mode 100644 index 00000000..1856aa2c --- /dev/null +++ b/moon_engine/moon_engine/api/authz/__init__.py @@ -0,0 +1,12 @@ +# 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. + + diff --git a/moon_engine/moon_engine/api/authz/authz.py b/moon_engine/moon_engine/api/authz/authz.py new file mode 100644 index 00000000..caf95f93 --- /dev/null +++ b/moon_engine/moon_engine/api/authz/authz.py @@ -0,0 +1,28 @@ +# 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. + + +import logging +from moon_engine.api.authz.managers import Managers + +logger = logging.getLogger("moon.engine.api.authz.pipeline") + + +class AuthzManager(Managers): + + def __init__(self, connector=None): + self.driver = connector.driver + Managers.AuthzManager = self + + def get_authz(self, subject_name, object_name, action_name): + return self.driver.get_authz(subject_name=subject_name, + object_name=object_name, + action_name=action_name) diff --git a/moon_engine/moon_engine/api/authz/managers.py b/moon_engine/moon_engine/api/authz/managers.py new file mode 100644 index 00000000..dedb148d --- /dev/null +++ b/moon_engine/moon_engine/api/authz/managers.py @@ -0,0 +1,20 @@ +# 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. + + +import logging + +logger = logging.getLogger("moon.engine.api.authz.managers") + + +class Managers(object): + """Object that links managers together""" + AuthzManager = None diff --git a/moon_engine/moon_engine/api/configuration.py b/moon_engine/moon_engine/api/configuration.py new file mode 100644 index 00000000..398497a1 --- /dev/null +++ b/moon_engine/moon_engine/api/configuration.py @@ -0,0 +1,195 @@ +# 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. + + +"""Configuration API""" +import hug.interface +import os +import logging +import logging.config +import yaml +import copy +from importlib.machinery import SourceFileLoader + +LOGGER = logging.getLogger("moon.engine.api.configuration") +__CONF = {} +CONF_FILE = "" + + +def init_logging(log_file=None): + """Initialize the logging system + + :return: nothing + """ + logging_conf = get_configuration(key='logging', file=log_file) + if get_configuration(key='debug', default=False): + logging_conf.get("handlers", {}).get("console", {})['level'] = logging.DEBUG + LOGGER.warning("Setting debug to True!") + logging.config.dictConfig(logging_conf) + + +def get_plugins_by_type(plugin_type): + """ + + :param plugin_type: + :return: + """ + plugins_dir = __CONF["plugins"]["directory"] + LOGGER.info("Getting all plugins for {}".format(plugin_type)) + import moon_engine.plugins + import glob + for plugname in glob.glob(os.path.join(moon_engine.plugins.__path__[0], "*.py")): + try: + plugname = os.path.basename(plugname)[:-3] + plug = __import__("moon_engine.plugins.{}".format(plugname), fromlist=["plugins", ]) + if getattr(plug, "PLUGIN_TYPE", "") == plugin_type: + yield plug + LOGGER.debug("Plug {} loaded".format(plugname)) + except ModuleNotFoundError: + pass + for plugname in glob.glob(os.path.join(plugins_dir, "*.py")): + m = SourceFileLoader("myplugs", os.path.join(plugins_dir, plugname+".py")) + plug = m.load_module() + if getattr(plug, "PLUGIN_TYPE", "") == plugin_type: + yield plug + LOGGER.debug("Plug {} loaded".format(plugname)) + + +def load_plugin(plugname): + """Load a python module + + :param plugname: the name of the module to load + :return: a reference to the module + """ + plugins_dir = __CONF["plugins"]["directory"] + LOGGER.info(f"load_plugin {plugname}") + try: + return __import__(plugname, fromlist=["plugins", ]) + except ImportError as e: + LOGGER.warning("Cannot import module ({})".format(e)) + try: + m = SourceFileLoader("myplugs", os.path.join(plugins_dir, plugname+".py")) + return m.load_module() + except ImportError as e: + LOGGER.error("Error in importing plugin {} from {}".format(plugname, plugins_dir)) + LOGGER.exception(e) + + +def get_authz_driver(): + """Load and check the plugin module + + :return: a reference to the module + """ + plug = load_plugin(__CONF["authorization"]["driver"]) + if plug.PLUGIN_TYPE != "authz": + raise Exception("Trying to load a bad Authz plugin (got {} plugin instead)".format( + plug.PLUGIN_TYPE)) + if "Connector" not in dir(plug): + raise Exception("Trying to load a bad Authz plugin (cannot find Connector)") + return plug + + +def get_orchestration_driver(): + """Load and check the plugin module + + :return: a reference to the module + """ + plug = load_plugin(__CONF["orchestration"]["driver"]) + if plug.PLUGIN_TYPE != "orchestration": + raise Exception("Trying to load a bad Orchestration plugin (got {} plugin instead)".format( + plug.PLUGIN_TYPE)) + if "Connector" not in dir(plug): + raise Exception("Trying to load a bad Orchestration plugin (cannot find Connector)") + return plug + + +def get_pipeline_driver(): + """Load and check the plugin module + + :return: a reference to the module + """ + plug = load_plugin(__CONF["information"]["driver"]) + if plug.PLUGIN_TYPE != "information": + raise Exception("Trying to load a bad Information plugin (got {} plugin instead)".format( + plug.PLUGIN_TYPE)) + if "Connector" not in dir(plug): + raise Exception("Trying to load a bad Information plugin (cannot find Connector)") + return plug + + +def search_config_file(filename): + """Look for the configuration file + + :param filename: a filename to search for + :return: the content of the configuration file + """ + data_config = None + for _filename in (filename, "moon.conf", "moon.yaml"): + for _dir in ( + "{}", + "/conf/{}", + "../{}", + "../conf/{}", + "/etc/moon/{}", + "conf/{}", + ): + _file = _dir.format(_filename) + try: + data_config = yaml.safe_load(open(_file)) + except FileNotFoundError: + data_config = None + continue + else: + LOGGER.warning("Configuration file: {}".format(_file)) + break + if data_config: + break + if not data_config: + LOGGER.error("Configuration file not found ({})...".format(filename)) + raise Exception("Configuration file not found ({})...".format(filename)) + return data_config + + +def set_configuration(conf): + """ Force the configuration dictionary + + :param conf: the configuration dictionary + :return: nothing + """ + global __CONF + __CONF = conf + + +def reload_configuration(): + global __CONF, CONF_FILE + __CONF = None + set_configuration(search_config_file(CONF_FILE)) + + +@hug.cli("get_conf") +@hug.local() +def get_configuration(key=None, default=None, file=None): + """ + List configuration attributes + :return: JSON configuration value + """ + global __CONF + if not __CONF: + if file: + __CONF = search_config_file(file) + else: + __CONF = search_config_file("moon.yaml") + init_logging() + if not key: + # TODO: delete passwords! + return copy.deepcopy(__CONF) + else: + return copy.deepcopy(__CONF.get(key, default)) diff --git a/moon_engine/moon_engine/api/import_json.py b/moon_engine/moon_engine/api/import_json.py new file mode 100644 index 00000000..d1296ff1 --- /dev/null +++ b/moon_engine/moon_engine/api/import_json.py @@ -0,0 +1,29 @@ +# 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. + + +"""Import JSON API""" +import hug + + +@hug.local() +@hug.post("/import/") +def import_json(body): + """Import data into the cache of the pipeline + + :return: OK if imported + """ + if "attributes" in body: + description = "Will update " + ", ".join(body.get("attributes")) + else: + description = "Will update all attributes" + # FIXME: dev the real import functionality + return {"status": "OK", "description": description} diff --git a/moon_engine/moon_engine/api/logs.py b/moon_engine/moon_engine/api/logs.py new file mode 100644 index 00000000..b6269648 --- /dev/null +++ b/moon_engine/moon_engine/api/logs.py @@ -0,0 +1,25 @@ +# 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. + + +"""Test hug API (local, command-line, and HTTP access)""" +import hug + + +@hug.local() +@hug.get("/logs/") +def list_logs(): + """List logs + + :return: JSON status output + """ + + return {"logs": []} diff --git a/moon_engine/moon_engine/api/orchestration/__init__.py b/moon_engine/moon_engine/api/orchestration/__init__.py new file mode 100644 index 00000000..1856aa2c --- /dev/null +++ b/moon_engine/moon_engine/api/orchestration/__init__.py @@ -0,0 +1,12 @@ +# 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. + + diff --git a/moon_engine/moon_engine/api/orchestration/managers.py b/moon_engine/moon_engine/api/orchestration/managers.py new file mode 100644 index 00000000..e07851c5 --- /dev/null +++ b/moon_engine/moon_engine/api/orchestration/managers.py @@ -0,0 +1,21 @@ +# 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. + + +import logging + +logger = logging.getLogger("moon.engine.api.orchestration.managers") + + +class Managers(object): + """Object that links managers together""" + SlaveManager = None + PipelineManager = None diff --git a/moon_engine/moon_engine/api/orchestration/pipeline.py b/moon_engine/moon_engine/api/orchestration/pipeline.py new file mode 100644 index 00000000..47a84409 --- /dev/null +++ b/moon_engine/moon_engine/api/orchestration/pipeline.py @@ -0,0 +1,51 @@ +# 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 uuid import uuid4 +import logging +from moon_utilities.security_functions import enforce +from moon_engine.api.orchestration.managers import Managers + +logger = logging.getLogger("moon.engine.api.orchestration.pipeline") + + +class PipelineManager(Managers): + + def __init__(self, connector=None): + self.driver = connector.driver + Managers.PipelineManager = self + + @enforce(("read", "write"), "pipelines") + def update_pipeline(self, moon_user_id, pipeline_id, data): + return self.driver.update_pipeline(pipeline_id=pipeline_id, data=data) + + @enforce("write", "pipelines") + def delete_pipeline(self, moon_user_id, pipeline_id): + return self.driver.delete_pipeline(pipeline_id=pipeline_id) + + @enforce("write", "pipelines") + def add_pipeline(self, moon_user_id, pipeline_id=None, data=None): + if not pipeline_id: + pipeline_id = uuid4().hex + if data is None: + data = {} + if "plugins" not in data: + data["plugins"] = ["moon_engine.plugins.authz"] + return self.driver.add_pipeline(pipeline_id=pipeline_id, data=data) + + @enforce("read", "pipelines") + def get_pipelines(self, moon_user_id, pipeline_id=None): + return self.driver.get_pipelines(pipeline_id=pipeline_id) + + @enforce("read", "pipelines") + def get_pipeline_api_key(self, moon_user_id, pipeline_id): + return self.driver.get_pipeline_api_key(pipeline_id=pipeline_id) diff --git a/moon_engine/moon_engine/api/orchestration/slave.py b/moon_engine/moon_engine/api/orchestration/slave.py new file mode 100644 index 00000000..fa4412ba --- /dev/null +++ b/moon_engine/moon_engine/api/orchestration/slave.py @@ -0,0 +1,44 @@ +# 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 uuid import uuid4 +import logging +from moon_utilities import exceptions +from moon_utilities.security_functions import enforce +from moon_engine.api.orchestration.managers import Managers + +logger = logging.getLogger("moon.manager.api.orchestration.pod") + + +class SlaveManager(Managers): + + def __init__(self, connector=None): + self.driver = connector.driver + Managers.SlaveManager = self + + @enforce(("read", "write"), "slaves") + def update_slave(self, user_id, slave_id, value): + self.driver.update_slave(slave_id=slave_id, value=value) + + @enforce("write", "slaves") + def delete_slave(self, user_id, slave_id): + self.driver.delete_slave(slave_id=slave_id) + + @enforce("write", "slaves") + def add_slave(self, user_id, slave_id=None, data=None): + if not slave_id: + slave_id = uuid4().hex + self.driver.add_slave(slave_id=slave_id, data=data) + + @enforce("read", "slaves") + def get_slaves(self, user_id, slave_id=None): + self.driver.get_slaves(slave_id=slave_id) diff --git a/moon_engine/moon_engine/api/pipeline/__init__.py b/moon_engine/moon_engine/api/pipeline/__init__.py new file mode 100644 index 00000000..582be686 --- /dev/null +++ b/moon_engine/moon_engine/api/pipeline/__init__.py @@ -0,0 +1,11 @@ +# 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. + diff --git a/moon_engine/moon_engine/api/pipeline/authz.py b/moon_engine/moon_engine/api/pipeline/authz.py new file mode 100644 index 00000000..02fb7d30 --- /dev/null +++ b/moon_engine/moon_engine/api/pipeline/authz.py @@ -0,0 +1,35 @@ +# 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. + + +import hug +from moon_engine.api.pipeline.validator import Validator + + +class Authz(object): + + @staticmethod + @hug.local() + @hug.get("/authz/{subject_name}/{object_name}/{action_name}") + def get(subject_name: hug.types.text, object_name: hug.types.text, + action_name: hug.types.text, response): + """Get a response on Main Authorization request + + :param subject_name: name of the subject or the request + :param object_name: name of the object + :param action_name: name of the action + :return: + "result": {true or false } + :internal_api: authz + """ + + validator = Validator() + response.status = validator.authz(subject_name, object_name, action_name) diff --git a/moon_engine/moon_engine/api/pipeline/update.py b/moon_engine/moon_engine/api/pipeline/update.py new file mode 100644 index 00000000..deaf9c12 --- /dev/null +++ b/moon_engine/moon_engine/api/pipeline/update.py @@ -0,0 +1,188 @@ +# 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. + + +"""Update API""" +import hug +from moon_utilities.auth_functions import api_key_authentication +from moon_engine.api.pipeline.update_pipeline import Update + + +class PipelineUpdate(object): + + @staticmethod + @hug.local() + @hug.put("/update/slave/{slave_id}", requires=api_key_authentication) + def update_slave(body, slave_id: hug.types.uuid, response): + """Tell the moon_engine wrapper that its cache should be updated + body may contain the attributes that the moon_engine should get from the manager + body example: + { + "name": "...", + "description": "..." + } + :return: 202 status code + """ + update_pipeline = Update() + response.status = update_pipeline.update_slaves(slave_id=str(slave_id).replace("-", ""), is_delete=False, + data=body) + + @staticmethod + @hug.local() + @hug.put("/update/pdp/{pdp_id}", requires=api_key_authentication) + def update_pdp(body, pdp_id: hug.types.uuid, response): + """Tell the moon_engine wrapper that its cache should be updated + body may contain the attributes that the moon_engine should get from the manager + if the attributes key is empty, all data should be retrieved + body example: + { + "vim_project_id": "...", + "security_pipeline": ["policy_id1", "policy_id2"], + "attributes": ["subjects", "subject_assignments", "subject_categories"] + } + :return: 202 status code + """ + + # todo call wrapper to update its pdp at the cache + update_pipeline = Update() + response.status = update_pipeline.update_pdp(is_delete=False, pdp_id=str(pdp_id).replace("-", ""), data=body) + + @staticmethod + @hug.local() + @hug.delete("/update/pdp/{pdp_id}", requires=api_key_authentication) + def delete_pdp(pdp_id: hug.types.uuid, response): + """Tell the moon_engine wrapper that its cache should be updated + body may contain the attributes that the moon_engine should get from the manager + if the attributes key is empty, all data should be retrieved + body example: + { + "vim_project_id": "...", + "security_pipeline": ["policy_id1", "policy_id2"], + "attributes": ["subjects", "subject_assignments", "subject_categories"] + } + :return: 202 status code + """ + + # todo call wrapper to update its pdp at the cache + update_pipeline = Update() + response.status = update_pipeline.update_pdp(is_delete=True, pdp_id=str(pdp_id).replace("-", "")) + + @staticmethod + @hug.local() + @hug.put("/update/policy/{policy_id}", requires=api_key_authentication) + def update_policy(body, policy_id: hug.types.uuid, response): + update_pipeline = Update() + response.status = update_pipeline.update_policy(is_delete=False, policy_id=str(policy_id).replace("-", ""), + data=body) + + @staticmethod + @hug.local() + @hug.delete("/update/policy/{policy_id}", requires=api_key_authentication) + def delete_policy(policy_id: hug.types.uuid, response): + update_pipeline = Update() + response.status = update_pipeline.update_policy(is_delete=True, policy_id=str(policy_id).replace("-", "")) + + @staticmethod + @hug.local() + @hug.delete("/update/assignment/{policy_id}/{type}/", requires=api_key_authentication) + @hug.delete("/update/assignment/{policy_id}/{type}/{perimeter_id}", + requires=api_key_authentication) + @hug.delete("/update/assignment/{policy_id}/{type}/{perimeter_id}/{category_id}", + requires=api_key_authentication) + @hug.delete("/update/assignment/{policy_id}/{type}/{perimeter_id}/{category_id}/{data_id}", + requires=api_key_authentication) + def delete_assignment(response, policy_id: hug.types.uuid, type: hug.types.text, + perimeter_id: hug.types.uuid = None, category_id: hug.types.uuid = None, + data_id: hug.types.uuid = None, authed_user: hug.directives.user=None): + update_pipeline = Update() + response.status = update_pipeline.delete_assignment(type=type, policy_id=str(policy_id).replace("-", ""), + perimeter_id=str(perimeter_id).replace("-", ""), + category_id=str(category_id).replace("-", ""), + data_id=data_id) + + @staticmethod + @hug.local() + @hug.put("/update/perimeter/{perimeter_id}/{policy_id}/{type}", requires=api_key_authentication) + def update_perimeter(body, perimeter_id: hug.types.uuid, policy_id: hug.types.uuid, + type: hug.types.text, response): + update_pipeline = Update() + response.status = update_pipeline.update_perimeter(is_delete=False, type=type, + perimeter_id=str(perimeter_id).replace("-", ""), data=body, + policy_id=str(policy_id).replace("-", "")) + + @staticmethod + @hug.local() + @hug.delete("/update/perimeter/{perimeter_id}/{policy_id}/{type}", requires=api_key_authentication) + def delete_perimeter(perimeter_id: hug.types.uuid, policy_id: hug.types.uuid, + type: hug.types.text, response): + update_pipeline = Update() + response.status = update_pipeline.update_perimeter(is_delete=True, type=type, + perimeter_id=str(perimeter_id).replace("-", ""), + policy_id=str(policy_id).replace("-", "")) + + @staticmethod + @hug.local() + @hug.delete("/update/rule/{policy_id}/{rule_id}", requires=api_key_authentication) + def delete_rule(policy_id: hug.types.uuid, rule_id: hug.types.uuid, response): + update_pipeline = Update() + response.status = update_pipeline.delete_rule(rule_id=str(rule_id).replace("-", ""), policy_id=str(policy_id).replace("-", "")) + + @staticmethod + @hug.local() + @hug.put("/update/model/{model_id}", requires=api_key_authentication) + def update_model(body, model_id: hug.types.uuid, response): + update_pipeline = Update() + response.status = update_pipeline.update_model(model_id=str(model_id).replace("-", ""), is_delete=False, + data=body) + + @staticmethod + @hug.local() + @hug.delete("/update/model/{model_id}", requires=api_key_authentication) + def delete_model(model_id: hug.types.uuid, response): + update_pipeline = Update() + response.status = update_pipeline.update_model(model_id=str(model_id).replace("-", ""), is_delete=True) + + @staticmethod + @hug.local() + @hug.delete("/update/meta_data/{category_id}/{type}", requires=api_key_authentication) + def delete_category(category_id: hug.types.uuid, type: hug.types.text, response): + update_pipeline = Update() + response.status = update_pipeline.delete_category(category_id=str(category_id).replace("-", ""), type=type) + + @staticmethod + @hug.local() + @hug.put("/update/meta_rule/{meta_rule_id}", requires=api_key_authentication) + def update_meta_rule(body, meta_rule_id: hug.types.uuid, response): + update_pipeline = Update() + response.status = update_pipeline.update_meta_rule(is_delete=False, + meta_rule_id=str(meta_rule_id).replace("-", ""), data=body) + + @staticmethod + @hug.local() + @hug.delete("/update/meta_rule/{meta_rule_id}", requires=api_key_authentication) + def delete_meta_rule(meta_rule_id: hug.types.uuid, response): + update_pipeline = Update() + response.status = update_pipeline.update_meta_rule(is_delete=True, + meta_rule_id=str(meta_rule_id).replace("-", "")) + + @staticmethod + @hug.local() + @hug.delete("/update/data/{data_id}/{type}", requires=api_key_authentication) + def delete_data(data_id: hug.types.uuid, type: hug.types.text, response): + update_pipeline = Update() + response.status = update_pipeline.delete_data(data_id=str(data_id).replace("-", ""), type=type) + + @staticmethod + @hug.local() + @hug.delete("/update/attributes/{name}", requires=api_key_authentication) + def delete_data(name: hug.types.text, response): + update_pipeline = Update() + response.status = update_pipeline.delete_attributes(name=name) diff --git a/moon_engine/moon_engine/api/pipeline/update_pipeline.py b/moon_engine/moon_engine/api/pipeline/update_pipeline.py new file mode 100644 index 00000000..3b312efb --- /dev/null +++ b/moon_engine/moon_engine/api/pipeline/update_pipeline.py @@ -0,0 +1,220 @@ +# 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. + + +import hug +from moon_cache.cache import Cache +from moon_engine.api.configuration import get_configuration +import logging + +logger = logging.getLogger("moon.engine.api.pipeline.update_pipeline") + + +class Update(object): + __CACHE = None + + def __init__(self): + if not self.__CACHE: + self.__CACHE = Cache.getInstance(manager_url=get_configuration("manager_url"), + incremental=get_configuration("incremental_updates"), + manager_api_key=get_configuration("api_token")) + + def update_policy(self, is_delete, policy_id, data=None): + + policies = self.__CACHE.policies + if is_delete: + if policy_id in policies: + del policies[policy_id] + else: + if policy_id in policies: + policies[policy_id] = data + else: + return hug.HTTP_208 + return hug.HTTP_202 + + def update_pdp(self, is_delete, pdp_id, data=None): + + pdps = self.__CACHE.pdp + if is_delete: + if pdp_id in pdps: + del pdps[pdp_id] + else: + if pdp_id in pdps: + pdps[pdp_id] = data + else: + return hug.HTTP_208 + return hug.HTTP_202 + + def delete_assignment(self, type, policy_id, perimeter_id=None, category_id=None, data_id=None): + + if type == "subject": + assignments = self.__CACHE.subject_assignments + if policy_id in assignments: + for key in assignments[policy_id]: + if (perimeter_id is None or assignments[policy_id][key]['subject_id'] == + perimeter_id) and ( + category_id is None or assignments[policy_id][key]['category_id'] == category_id): + if data_id is None or data_id in assignments[policy_id][key]['assignments']: + assignments[policy_id][key]['assignments'].remove(data_id) + if len(assignments[policy_id][key]['assignments']) == 0: + del assignments[policy_id][key]; + else: + del assignments[policy_id][key] + break + + elif type == "object": + assignments = self.__CACHE.object_assignments + if policy_id in assignments: + for key in assignments[policy_id]: + if (perimeter_id is None or assignments[policy_id][key]['object_id'] == + perimeter_id) and ( + category_id is None or assignments[policy_id][key]['category_id'] == category_id): + if data_id is None or data_id in assignments[policy_id][key]['assignments']: + assignments[policy_id][key]['assignments'].remove(data_id) + if len(assignments[policy_id][key]['assignments']) == 0: + del assignments[policy_id][key]; + else: + del assignments[policy_id][key] + break + else: + assignments = self.__CACHE.action_assignments + if policy_id in assignments: + for key in assignments[policy_id]: + if (perimeter_id is None or assignments[policy_id][key]['action_id'] == + perimeter_id) and ( + category_id is None or assignments[policy_id][key]['category_id'] == category_id): + if data_id is None or data_id in assignments[policy_id][key]['assignments']: + assignments[policy_id][key]['assignments'].remove(data_id) + if len(assignments[policy_id][key]['assignments']) == 0: + del assignments[policy_id][key]; + else: + del assignments[policy_id][key] + break + return hug.HTTP_202 + + def update_perimeter(self, is_delete, type, perimeter_id, data=None, policy_id=None): + + if is_delete: + if type == "subject": + perimeters = self.__CACHE.subjects + if policy_id in perimeters and perimeter_id in perimeters[policy_id] and \ + policy_id in perimeters[policy_id][perimeter_id]['policy_list']: + del perimeters[policy_id][perimeter_id] + elif type == "object": + perimeters = self.__CACHE.objects + if policy_id in perimeters and perimeter_id in perimeters[policy_id] and \ + policy_id in perimeters[policy_id][perimeter_id]['policy_list']: + del perimeters[policy_id][perimeter_id] + else: + perimeters = self.__CACHE.actions + if policy_id in perimeters and perimeter_id in perimeters[policy_id] and \ + policy_id in perimeters[policy_id][perimeter_id]['policy_list']: + del perimeters[policy_id][perimeter_id] + else: + if type == "subject": + perimeters = self.__CACHE.subjects + if policy_id in perimeters and perimeter_id in perimeters[policy_id] and \ + policy_id in perimeters[policy_id][perimeter_id]['policy_list']: + perimeters[policy_id][perimeter_id]['name'] = data['name'] + perimeters[policy_id][perimeter_id]['description'] = data['description'] + else: + return hug.HTTP_208 + elif type == "object": + perimeters = self.__CACHE.objects + if policy_id in perimeters and perimeter_id in perimeters[policy_id] and \ + policy_id in perimeters[policy_id][perimeter_id]['policy_list']: + perimeters[policy_id][perimeter_id]['name'] = data['name'] + perimeters[policy_id][perimeter_id]['description'] = data['description'] + else: + return hug.HTTP_208 + else: + perimeters = self.__CACHE.actions + if policy_id in perimeters and perimeter_id in perimeters[policy_id] and \ + policy_id in perimeters[policy_id][perimeter_id]['policy_list']: + perimeters[policy_id][perimeter_id]['name'] = data['name'] + perimeters[policy_id][perimeter_id]['description'] = data['description'] + else: + return hug.HTTP_208 + return hug.HTTP_202 + + def delete_rule(self, rule_id, policy_id): + + rules = self.__CACHE.rules + if policy_id in rules and rule_id in rules[policy_id]: + del rules[policy_id][rule_id] + return hug.HTTP_202 + + def update_model(self, model_id, is_delete, data=None): + if is_delete: + models = self.__CACHE.models + if model_id in models: + del models[model_id] + else: + models = self.__CACHE.models + if model_id in models: + models[model_id] = data + else: + return hug.HTTP_208 + return hug.HTTP_202 + + def delete_category(self, category_id, type): + + if type == "subject": + categories = self.__CACHE.subject_categories + if category_id in categories: + del categories[category_id] + elif type == 'object': + categories = self.__CACHE.object_categories + if category_id in categories: + del categories[category_id] + else: + categories = self.__CACHE.action_categories + if category_id in categories: + del categories[category_id] + return hug.HTTP_202 + + def update_meta_rule(self, is_delete, meta_rule_id, data=None): + + if is_delete: + meta_rules = self.__CACHE.meta_rules + if meta_rule_id in meta_rules: + del meta_rules[meta_rule_id] + else: + meta_rules = self.__CACHE.meta_rules + if meta_rule_id in meta_rules: + meta_rules[meta_rule_id] = data + else: + return hug.HTTP_208 + return hug.HTTP_202 + + def delete_data(self, data_id, type): + + if type == 'subject': + data = self.__CACHE.subject_data + if data_id in data: + del data[data_id] + elif type == 'object': + data = self.__CACHE.object_data + if data_id in data: + del data[data_id] + else: + data = self.__CACHE.action_data + if data_id in data: + del data[data_id] + + return hug.HTTP_202 + + def delete_attributes(self, name): + + attributes = self.__CACHE.attributes + self.__CACHE.set_attribute(name) + + return hug.HTTP_202 diff --git a/moon_engine/moon_engine/api/pipeline/validator.py b/moon_engine/moon_engine/api/pipeline/validator.py new file mode 100644 index 00000000..1fc8588a --- /dev/null +++ b/moon_engine/moon_engine/api/pipeline/validator.py @@ -0,0 +1,125 @@ +# 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 moon_cache.context import Context +from moon_utilities import exceptions +import itertools +import logging +import hug +from moon_cache.cache import Cache +from moon_engine.api.configuration import get_configuration + +LOGGER = logging.getLogger("moon.authz.api." + __name__) + + +class Validator(object): + __CACHE = None + + def __init__(self): + + self.__CACHE = Cache.getInstance(manager_url=get_configuration("manager_url"), + incremental=get_configuration("incremental_updates"), + manager_api_key=get_configuration("api_token")) + self.context = None + + def authz(self, subject_name, object_name, action_name): + + ctx = { + "pdp_id": get_configuration("uuid"), + "subject_name": subject_name, + "object_name": object_name, + "action_name": action_name + } + self.context = Context(ctx, self.__CACHE) + + self.context.set_cache(self.__CACHE) + self.context.increment_index() + response = self.__authz_request() + self.context.delete_cache() + return response + + def __authz_request(self): + + LOGGER.debug("self.context.pdp_set={}".format(self.context.pdp_set)) + result, message = self.__check_rules() + if result: + if self.__exec_instructions(result): + return hug.HTTP_204 + else: + self.context.current_state = "deny" + return hug.HTTP_403 + + def __check_rules(self): + + scopes_list = list() + current_header_id = self.context.headers[self.context.index] + if not self.context.pdp_set: + raise exceptions.PdpUnknown + if current_header_id not in self.context.pdp_set: + raise Exception('Invalid index') + current_pdp = self.context.pdp_set[current_header_id] + category_list = list() + if 'meta_rules' not in current_pdp: + raise exceptions.PdpContentError + try: + category_list.extend(current_pdp["meta_rules"]["subject_categories"]) + category_list.extend(current_pdp["meta_rules"]["object_categories"]) + category_list.extend(current_pdp["meta_rules"]["action_categories"]) + except Exception: + raise exceptions.MetaRuleContentError + if 'target' not in current_pdp: + raise exceptions.PdpContentError + for category in category_list: + scope = list(current_pdp['target'][category]) + scopes_list.append(scope) + if self.context.current_policy_id not in self.__CACHE.rules: + raise exceptions.PolicyUnknown + if 'rules' not in self.__CACHE.rules[self.context.current_policy_id]: + raise exceptions.RuleUnknown + + for item in itertools.product(*scopes_list): + req = list(item) + for rule in self.__CACHE.rules[self.context.current_policy_id]["rules"]: + if req == rule['rule']: + return rule['instructions'], "" + if not list(itertools.product(*scopes_list)): + LOGGER.error("There is an error in retrieved scopes ({})".format(scopes_list)) + cat_list = [] + categories = dict(self.__CACHE.subject_categories) + categories.update(dict(self.__CACHE.object_categories)) + categories.update(dict(self.__CACHE.action_categories)) + for category in category_list: + if category.startswith("attributes:"): + cat_list.append(category) + else: + cat_list.append(categories[category].get('name')) + LOGGER.error("Categories are ({})".format(", ".join(cat_list))) + return False, "There is an error in retrieved scopes" + LOGGER.warning("No rule match the request...") + return False, "No rule match the request..." + + def __exec_instructions(self, instructions): + + for instruction in instructions: + for key in instruction: + if key == "decision": + if instruction["decision"] == "grant": + self.context.current_state = "grant" + LOGGER.info("__exec_instructions True {}".format( + self.context.current_state)) + return True + else: + self.context.current_state = instruction["decision"].lower() + + LOGGER.info("__exec_instructions False {}".format(self.context.current_state)) + + return False diff --git a/moon_engine/moon_engine/api/status.py b/moon_engine/moon_engine/api/status.py new file mode 100644 index 00000000..2a44a865 --- /dev/null +++ b/moon_engine/moon_engine/api/status.py @@ -0,0 +1,30 @@ +# 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. + + +"""Status API""" +import hug +from moon_engine.api import configuration + +@hug.local() +@hug.get("/status/") +def list_status(): + """ + List statuses + :return: JSON status output + """ + + return {"status": { + "uuid": configuration.get_configuration("uuid"), + "type": configuration.get_configuration("type"), + "log" : configuration.get_configuration("logging").get( + "handlers", {}).get("file", {}).get("filename", "") + }} diff --git a/moon_engine/moon_engine/api/wrapper/__init__.py b/moon_engine/moon_engine/api/wrapper/__init__.py new file mode 100644 index 00000000..582be686 --- /dev/null +++ b/moon_engine/moon_engine/api/wrapper/__init__.py @@ -0,0 +1,11 @@ +# 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. + diff --git a/moon_engine/moon_engine/api/wrapper/api/__init__.py b/moon_engine/moon_engine/api/wrapper/api/__init__.py new file mode 100644 index 00000000..582be686 --- /dev/null +++ b/moon_engine/moon_engine/api/wrapper/api/__init__.py @@ -0,0 +1,11 @@ +# 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. + diff --git a/moon_engine/moon_engine/api/wrapper/api/authz.py b/moon_engine/moon_engine/api/wrapper/api/authz.py new file mode 100644 index 00000000..4d1e4a84 --- /dev/null +++ b/moon_engine/moon_engine/api/wrapper/api/authz.py @@ -0,0 +1,43 @@ +# 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. + + +"""Authz API""" + +import hug +from moon_engine.api.wrapper.router import Router + + +class Authz(object): + """ + Endpoint for Authz requests + """ + + @staticmethod + @hug.local() + @hug.get("/authz/{project_id}/{subject_name}/{object_name}/{action_name}") + def get(project_id: hug.types.text, subject_name: hug.types.text, object_name: hug.types.text, + action_name: hug.types.text): + """Get a response on Main Authorization request + + :param project_id: uuid of the project + :param subject_name: name of the subject or the request + :param object_name: name of the object + :param action_name: name of the action + :return: + "result": {true or false } + :internal_api: authz + """ + + with Router(project_id, subject_name, object_name, action_name) as router: + + response = router.auth_request() + return response diff --git a/moon_engine/moon_engine/api/wrapper/api/pipeline.py b/moon_engine/moon_engine/api/wrapper/api/pipeline.py new file mode 100644 index 00000000..19b9578a --- /dev/null +++ b/moon_engine/moon_engine/api/wrapper/api/pipeline.py @@ -0,0 +1,100 @@ +# 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. + + +"""Pipeline API""" +import hug +from moon_utilities.auth_functions import api_key_authentication +from moon_engine import orchestration_driver +from moon_utilities.security_functions import validate_input +from moon_engine.api import configuration +from moon_cache.cache import Cache + +CACHE = Cache.getInstance(manager_url=configuration.get_configuration("manager_url"), + incremental=configuration.get_configuration("incremental_updates"), + manager_api_key=configuration.get_configuration("api_token")) + + +class Pipeline(object): + """ + Endpoint for pipelines requests + """ + + @staticmethod + @hug.local() + @hug.get("/pipelines", requires=api_key_authentication) + @hug.get("/pipeline/{uuid}", requires=api_key_authentication) + def get(uuid: hug.types.uuid=None, authed_user: hug.directives.user=None): + """Retrieve all pipelines + + :param uuid: uuid of the pipeline + :param authed_user: the name of the authenticated user + :return: { + "pipeline_id1": { + "name": "...", + "description": "... (optional)", + } + } + """ + uuid = str(uuid).replace("-", "") + orchestration_driver.init() + data = orchestration_driver.PipelineManager.get_pipelines(moon_user_id=authed_user, + pipeline_id=uuid) + return {"pipelines": data} + + @staticmethod + @hug.local() + @hug.put("/pipeline/{uuid}", requires=api_key_authentication) + def put(uuid: hug.types.uuid, body: validate_input("name"), + authed_user: hug.directives.user = None): + """ + Ask for the creation of a new pipeline + :param uuid: uuid of the pipeline + :param body: body of the request + :param authed_user: the name of the authenticated user + :return: { + "name": "my_pdp", + "description": "...", + "vim_project_id": "an existing ID", + "security_pipelines": ["an existing policy ID", ], + "slave": ["name of a slave", ] + } + """ + uuid = str(uuid).replace("-", "") + orchestration_driver.init() + data = orchestration_driver.PipelineManager.add_pipeline(moon_user_id=authed_user, + pipeline_id=uuid, + data=body) + CACHE.add_pipeline(uuid, data) + return {"pipelines": data} + + @staticmethod + @hug.local() + @hug.delete("/pipeline/{uuid}", requires=api_key_authentication) + def delete(uuid: hug.types.uuid, authed_user: hug.directives.user = None): + """ + Ask for the deletion of a new pipeline + :param uuid: uuid of the pipeline + :param authed_user: the name of the authenticated user + :return: { + "name": "my_pdp", + "description": "...", + "vim_project_id": "an existing ID", + "security_pipelines": ["an existing policy ID", ], + "slave": ["name of a slave", ] + } + """ + uuid = str(uuid).replace("-", "") + orchestration_driver.init() + orchestration_driver.PipelineManager.delete_pipeline(moon_user_id=authed_user, + pipeline_id=uuid) + CACHE.delete_pipeline(uuid) + return True diff --git a/moon_engine/moon_engine/api/wrapper/api/update.py b/moon_engine/moon_engine/api/wrapper/api/update.py new file mode 100644 index 00000000..7af274e5 --- /dev/null +++ b/moon_engine/moon_engine/api/wrapper/api/update.py @@ -0,0 +1,179 @@ +# 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. + + +"""Update API""" +import hug +from moon_utilities.auth_functions import api_key_authentication +from moon_engine.api.wrapper.update_wrapper import UpdateWrapper as UpdateWrapper + + +class WrapperUpdate(object): + + @staticmethod + @hug.local() + @hug.put("/update", requires=api_key_authentication) + def update(body, response, authed_user: hug.directives.user): + """Tell the moon_engine wrapper that its own data should be updated + It simply reloads the conf file + + :return: 204 status code + """ + + # todo call wrapper to update its pdp at the cache + update_wrapper = UpdateWrapper() + response.status = update_wrapper.update_wrapper(data=body, moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.put("/update/pdp/{pdp_id}", requires=api_key_authentication) + def update_pdp(body, pdp_id: hug.types.uuid, response, authed_user: hug.directives.user): + """Tell the moon_engine wrapper that its cache should be updated + body may contain the attributes that the moon_engine should get from the manager + if the attributes key is empty, all data should be retrieved + body example: + { + "vim_project_id": "...", + "security_pipeline": ["policy_id1", "policy_id2"], + "attributes": ["subjects", "subject_assignments", "subject_categories"] + } + :return: 202 status code + """ + + # todo call wrapper to update its pdp at the cache + update_wrapper = UpdateWrapper() + response.status = update_wrapper.update_pdp(pdp_id=str(pdp_id).replace("-", ""), data=body, moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/pdp/{pdp_id}", requires=api_key_authentication) + def delete_pdp(pdp_id: hug.types.uuid, response, authed_user: hug.directives.user): + """Tell the moon_engine wrapper that its cache should be updated + body may contain the attributes that the moon_engine should get from the manager + if the attributes key is empty, all data should be retrieved + body example: + { + "vim_project_id": "...", + "security_pipeline": ["policy_id1", "policy_id2"], + "attributes": ["subjects", "subject_assignments", "subject_categories"] + } + :return: 202 status code + """ + + # todo call wrapper to update its pdp at the cache + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_pdp(pdp_id=str(pdp_id).replace("-", ""), moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.put("/update/policy/{policy_id}", requires=api_key_authentication) + def update_policy(body, policy_id: hug.types.uuid, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.update_policy(policy_id=str(policy_id).replace("-", ""), data=body, moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/policy/{policy_id}", requires=api_key_authentication) + def delete_policy(policy_id: hug.types.uuid, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_policy(policy_id=str(policy_id).replace("-", ""), moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/assignment/{policy_id}/{type}/", requires=api_key_authentication) + @hug.delete("/update/assignment/{policy_id}/{type}/{perimeter_id}", requires=api_key_authentication) + @hug.delete("/update/assignment/{policy_id}/{type}/{perimeter_id}/{category_id}", requires=api_key_authentication) + @hug.delete("/update/assignment/{policy_id}/{type}/{perimeter_id}/{category_id}/{data_id}", requires=api_key_authentication) + def delete_assignment(response, policy_id: hug.types.uuid, type: hug.types.text, + perimeter_id: hug.types.uuid = None, category_id: hug.types.uuid = None, + data_id: hug.types.uuid = None, authed_user: hug.directives.user=None): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_assignment(type=type, policy_id=str(policy_id).replace("-", ""), + perimeter_id=str(perimeter_id).replace("-", ""), + category_id=str(category_id).replace("-", ""), + data_id=data_id, moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.put("/update/perimeter/{perimeter_id}/{policy_id}/{type}", requires=api_key_authentication) + def update_perimeter(body, perimeter_id: hug.types.uuid, policy_id: hug.types.uuid, + type: hug.types.text, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.update_perimeter( type=type, + perimeter_id=str(perimeter_id).replace("-", ""), data=body, + policy_id=str(policy_id).replace("-", ""), moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/perimeter/{perimeter_id}/{policy_id}/{type}", requires=api_key_authentication) + def delete_perimeter(perimeter_id: hug.types.uuid, policy_id: hug.types.uuid, + type: hug.types.text, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_perimeter(type=type, + perimeter_id=str(perimeter_id).replace("-", ""), + policy_id=str(policy_id).replace("-", ""), moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/rule/{policy_id}/{rule_id}", requires=api_key_authentication) + def delete_rule(policy_id: hug.types.uuid, rule_id: hug.types.uuid, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_rule(rule_id=str(rule_id).replace("-", ""), policy_id=str(policy_id).replace("-", ""), moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.put("/update/model/{model_id}", requires=api_key_authentication) + def update_model(body, model_id: hug.types.uuid, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.update_model(model_id=str(model_id).replace("-", ""), data=body, moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/model/{model_id}", requires=api_key_authentication) + def delete_model(model_id: hug.types.uuid, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_model(model_id=str(model_id).replace("-", ""), moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/meta_data/{category_id}/{type}", requires=api_key_authentication) + def delete_category(category_id: hug.types.uuid, type: hug.types.text, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_category(category_id=str(category_id).replace("-", ""), type=type, moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.put("/update/meta_rule/{meta_rule_id}", requires=api_key_authentication) + def update_meta_rule(body, meta_rule_id: hug.types.uuid, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.update_meta_rule(meta_rule_id=str(meta_rule_id).replace("-", ""), data=body, moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/meta_rule/{meta_rule_id}", requires=api_key_authentication) + def delete_meta_rule(meta_rule_id: hug.types.uuid, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_meta_rule(meta_rule_id=str(meta_rule_id).replace("-", ""), moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/data/{data_id}/{type}", requires=api_key_authentication) + def delete_data(data_id: hug.types.uuid, type: hug.types.text, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_data(data_id=str(data_id).replace("-", ""), type=type, moon_user_id=authed_user) + + @staticmethod + @hug.local() + @hug.delete("/update/attributes/{name}", requires=api_key_authentication) + def delete_data(name: str, response, authed_user: hug.directives.user): + update_wrapper = UpdateWrapper() + response.status = update_wrapper.delete_attributes(name=name, moon_user_id=authed_user) + diff --git a/moon_engine/moon_engine/api/wrapper/router.py b/moon_engine/moon_engine/api/wrapper/router.py new file mode 100644 index 00000000..db6b6e24 --- /dev/null +++ b/moon_engine/moon_engine/api/wrapper/router.py @@ -0,0 +1,115 @@ +# 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 moon_utilities import exceptions +from moon_cache.cache import Cache +from uuid import uuid4 +import logging +import requests +from moon_engine.api.configuration import get_configuration + +LOGGER = logging.getLogger("moon.engine.wrapper." + __name__) + + +class Router(object): + __CACHE = None + + def __init__(self, project_id, subject_name, object_name, action_name): + + if not self.__CACHE: + self.__CACHE = Cache.getInstance(manager_url=get_configuration("manager_url"), + incremental=get_configuration("incremental_updates"), + manager_api_key=get_configuration("api_token")) + + self.pipeline_id = self.__check_pdp_from_cache(project_id) + + self.request_id = uuid4().hex + + self.ctx = { + "project_id": project_id, + "subject_name": subject_name, + "object_name": object_name, + "action_name": action_name + } + + # ToDo add status of request + self.__CACHE.authz_requests[self.request_id] = {} + + pdp_id = self.__CACHE.get_pdp_from_vim_project(project_id) + self.__CACHE.update(pipeline=pdp_id) + self.pipeline = [] + if self.pipeline_id in self.__CACHE.pipelines: + self.pipeline = self.__CACHE.pipelines[self.pipeline_id] + + if len(self.pipeline) == 0 or not all( + k in self.pipeline for k in ("host", "port")): + raise exceptions.MoonError('Void container chaining') + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + self.__CACHE.authz_requests.pop(self.request_id) + + def auth_request(self): + req = None + endpoint = self.__CACHE.get_pipeline_url(self.ctx["project_id"]) + + try: + req = requests.get("{}/authz/{}/{}/{}".format( + endpoint, + self.ctx["subject_name"], + self.ctx["object_name"], + self.ctx["action_name"]), + timeout=2 + ) + + if req.status_code != 200 and req.status_code != 202 and req.status_code != 204: + raise exceptions.AuthzException( + "Receive bad response from Authz function (with address - {})" + .format(req.status_code)) + + except requests.exceptions.ConnectionError: + LOGGER.error("Cannot connect to {}".format( + "{}/authz".format(endpoint)) + ) + except requests.exceptions.ReadTimeout: + LOGGER.error("Timeout error") + return {"result": False, "message": "Timeout during request for pipeline"}, 400 + except Exception as e: + LOGGER.error("Unexpected error:", e) + return {"result": False, "message": e}, 400 + + if not req: + raise exceptions.AuthzException("Cannot connect to Authz function") + + if req.status_code == 204: + return {"result": True, "message": ""} + return {"result": False, "message": req.content}, 400 + + def __check_pdp_from_cache(self, uuid): + """Check if a PDP exist with this ID in the cache of this component + + :param uuid: Keystone Project ID + :return: True or False + """ + + if self.__CACHE.get_pdp_from_vim_project(uuid): + return self.__CACHE.get_pipeline_id_from_project_id(uuid) + + self.__CACHE.update() + + if self.__CACHE.get_pdp_from_vim_project(uuid): + return self.__CACHE.get_pipeline_id_from_project_id(uuid) + + raise exceptions.MoonError("Unknown Project ID {}".format(uuid)) + diff --git a/moon_engine/moon_engine/api/wrapper/update_wrapper.py b/moon_engine/moon_engine/api/wrapper/update_wrapper.py new file mode 100644 index 00000000..bb388472 --- /dev/null +++ b/moon_engine/moon_engine/api/wrapper/update_wrapper.py @@ -0,0 +1,233 @@ +# 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. + + +import logging +import sys + +import hug +import requests +from moon_engine import orchestration_driver +from moon_cache.cache import Cache +from moon_engine.api.configuration import get_configuration, reload_configuration +from moon_utilities import exceptions + +logger = logging.getLogger("moon.engine.api.wrapper" + __name__) + + +class UpdateWrapper(object): + __CACHE = None + + def __init__(self): + if not self.__CACHE: + self.__CACHE = Cache.getInstance( + manager_url=get_configuration("manager_url"), + incremental=get_configuration("incremental_updates"), + manager_api_key=get_configuration("api_token")) + + def update_wrapper(self, data, moon_user_id=None): + reload_configuration() + return hug.HTTP_204 + + def update_pdp(self, pdp_id, data, moon_user_id=None): + + url_pattern = "update/pdp/{}".format(pdp_id) + return self.__process_request(url_pattern, body=data, moon_user_id=moon_user_id, pdp_id=pdp_id) + + def delete_pdp(self, pdp_id, moon_user_id=None): + + url_pattern = "update/pdp/{}".format(pdp_id) + return self.__process_request(url_pattern, moon_user_id=moon_user_id, pdp_id=pdp_id, delete=True) + + def delete_policy(self, policy_id, moon_user_id=None): + url_pattern = "update/policy/{}".format(policy_id) + return self.__process_request(url_pattern, moon_user_id, policy_id=policy_id, delete=True) + + def update_policy(self, policy_id, data=None, moon_user_id=None): + + url_pattern = "update/policy/{}".format(policy_id) + return self.__process_request(url_pattern, body=data, moon_user_id=moon_user_id, + policy_id=policy_id) + + def delete_assignment(self, type, policy_id, perimeter_id=None, category_id=None, data_id=None, + moon_user_id=None): + + if policy_id and perimeter_id and category_id and data_id: + url_pattern = "update/assignment/{}/{}/{}/{}/{}".format(policy_id, type, perimeter_id, + category_id, + data_id) + + if policy_id and perimeter_id and category_id: + url_pattern = "update/assignment/{}/{}/{}/{}".format(policy_id, type, perimeter_id, + category_id) + + if policy_id and perimeter_id: + url_pattern = "update/assignment/{}/{}/{}".format(policy_id, type, perimeter_id) + + if policy_id: + url_pattern = "update/assignment/{}/{}".format(policy_id, type) + + return self.__process_request(url_pattern, moon_user_id, policy_id=policy_id, delete=True) + + def update_perimeter(self, type, perimeter_id, data=None, policy_id=None, moon_user_id=None): + url_pattern = "update/perimeter/{}/{}/{}".format(perimeter_id, policy_id, type) + return self.__process_request(url_pattern, body=data, moon_user_id=moon_user_id, + policy_id=policy_id) + + def delete_perimeter(self, type, perimeter_id, policy_id=None, moon_user_id=None): + url_pattern = "update/perimeter/{}/{}/{}".format(perimeter_id, policy_id, type) + return self.__process_request(url_pattern, moon_user_id, policy_id=policy_id, delete=True) + + def delete_rule(self, rule_id, policy_id, moon_user_id=None): + + url_pattern = "update/rule/{}/{}".format(policy_id, rule_id) + return self.__process_request(url_pattern, moon_user_id=moon_user_id, policy_id=policy_id, + delete=True) + + def update_model(self, model_id, data=None, moon_user_id=None): + + url_pattern = "update/model/{}".format(model_id) + return self.__process_request(url_pattern, body=data, moon_user_id=moon_user_id) + + def delete_model(self, model_id, moon_user_id=None): + + url_pattern = "update/model/{}".format(model_id) + return self.__process_request(url_pattern, moon_user_id=moon_user_id, delete=True) + + def delete_category(self, category_id, type, moon_user_id=None): + + url_pattern = "update/meta_data/{}/{}".format(category_id, type) + return self.__process_request(url_pattern, moon_user_id=moon_user_id, delete=True) + + def update_meta_rule(self, meta_rule_id, data=None, moon_user_id=None): + + url_pattern = "update/meta_rule/{}".format(meta_rule_id, type) + return self.__process_request(url_pattern, body=data, moon_user_id=moon_user_id) + + def delete_meta_rule(self, meta_rule_id, moon_user_id=None): + + url_pattern = "update/meta_rule/{}".format(meta_rule_id, type) + return self.__process_request(url_pattern, moon_user_id=moon_user_id, delete=True) + + def delete_data(self, data_id, type, moon_user_id=None): + + url_pattern = "update/data/{}/{}".format(data_id, type) + return self.__process_request(url_pattern, moon_user_id=moon_user_id, delete=True) + + def delete_attributes(self, name, moon_user_id=None): + + url_pattern = "update/attributes/{}".format(name) + return self.__process_request(url_pattern, moon_user_id=moon_user_id, delete=True) + + def __process_request(self, url_pattern, body=None, moon_user_id=None, policy_id=None, + pdp_id=None, delete=False): + if policy_id: + endpoint = self.__CACHE.get_pipeline_url(pipeline_id=policy_id) + cached_api_key = self.__CACHE.get_api_key(pipeline_id=policy_id) + if orchestration_driver.PipelineManager: + _pdp_id = self.__CACHE.get_pdp_id_from_policy_id(policy_id=policy_id) + api_key = orchestration_driver.PipelineManager.get_pipeline_api_key(moon_user_id=moon_user_id, pipeline_id=_pdp_id) + if not api_key: + api_key = cached_api_key + + if not endpoint: + return hug.HTTP_208 + if delete: + return self.__execute_delete_request(endpoint, url_pattern, api_key) + else: + return self.__execute_put_request(endpoint, url_pattern, api_key, body) + + elif pdp_id: + endpoint = self.__CACHE.get_pipeline_url(pdp_id=pdp_id) + cached_api_key = self.__CACHE.get_api_key(pdp_id=pdp_id) + if orchestration_driver.PipelineManager: + api_key = orchestration_driver.PipelineManager.get_pipeline_api_key(moon_user_id=moon_user_id, pipeline_id=pdp_id) + if not api_key: + api_key = cached_api_key + + if not endpoint: + return hug.HTTP_208 + if delete: + return self.__execute_delete_request(endpoint, url_pattern, api_key) + else: + return self.__execute_put_request(endpoint, url_pattern, api_key, body) + + else: + pdps = self.__CACHE.pdp + for _pdp_id in pdps: + vim_project_id = pdps.get(_pdp_id, {}).get("vim_project_id") + if vim_project_id: + cached_api_key = self.__CACHE.get_api_key(project_id=vim_project_id) + if orchestration_driver.PipelineManager: + api_key = orchestration_driver.PipelineManager.get_pipeline_api_key(moon_user_id=moon_user_id, pipeline_id=_pdp_id) + if not api_key: + api_key = cached_api_key + + + endpoint = self.__CACHE.get_pipeline_url(project_id=vim_project_id) + + if delete: + return self.__execute_delete_request(endpoint, url_pattern, api_key) + else: + return self.__execute_put_request(endpoint, url_pattern, api_key, body) + return hug.HTTP_206 + + @staticmethod + def __execute_put_request(endpoint, url_pattern, api_key, body): + logger.info(f"Sending a PUT request on {endpoint}/{url_pattern}") + try: + req = requests.put("{}/{}".format( + endpoint, url_pattern), headers={"X-Api-Key": api_key}, json=body) + logger.info(req) + if req.status_code == 200: + return hug.HTTP_200 + if req.status_code == 202: + return hug.HTTP_202 + if req.status_code == 208: + return hug.HTTP_208 + + else: + raise exceptions.AuthzException( + "Receive bad response from Authz function " + "(with address - {})".format(req.status_code)) + + except requests.exceptions.ConnectionError: + logger.error("Cannot connect to {}".format( + "{}/authz/{}".format(endpoint, url_pattern)) + ) + except Exception as e: + logger.exception("Unexpected error:", e) + + @staticmethod + def __execute_delete_request(endpoint, url_pattern, api_key): + logger.info(f"Sending a DELETE request on {endpoint}/{url_pattern}") + try: + req = requests.delete("{}/{}".format( + endpoint, url_pattern), headers={"X-Api-Key": api_key}) + + if req.status_code == 200: + return hug.HTTP_200 + if req.status_code == 202: + return hug.HTTP_202 + if req.status_code == 208: + return hug.HTTP_208 + + else: + raise exceptions.AuthzException( + "Receive bad response from Authz function " + "(with address - {})".format(req.status_code)) + + except requests.exceptions.ConnectionError: + logger.error("Cannot connect to {}".format( + "{}/authz/{}".format(endpoint, url_pattern)) + ) + except Exception as e: + logger.exception("Unexpected error:", e) diff --git a/moon_engine/moon_engine/authz_driver.py b/moon_engine/moon_engine/authz_driver.py new file mode 100644 index 00000000..d6f0284b --- /dev/null +++ b/moon_engine/moon_engine/authz_driver.py @@ -0,0 +1,68 @@ +# 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. + + +"""Authorization compute Driver""" + +import logging +from moon_engine.api import configuration +from moon_engine.api.authz import authz + +LOGGER = logging.getLogger("moon.manager.authz_driver") + + +AuthzManager = None + + +class Driver: + """ + Generic driver + """ + + def __init__(self, driver_name, engine_name): + self.name = driver_name + self.plug = configuration.get_authz_driver() + self.driver = self.plug.Connector(driver_name, engine_name) + + +class AuthzDriver(Driver): + """ + Driver for authorization computation + """ + + def __init__(self, driver_name, engine_name): + super(AuthzDriver, self).__init__(driver_name, engine_name) + self.engine = engine_name + + def get_authz(self, subject_name, object_name, action_name): + """ + Get the result of the authorization process + :param subject_name: + :param object_name: + :param action_name: + :return: + """ + raise NotImplementedError() # pragma: no cover + + +def init(): + """Initialize the managers + + :return: nothing + """ + global AuthzManager + + LOGGER.info("Initializing driver") + conf = configuration.get_configuration("authorization") + + AuthzManager = authz.AuthzManager( + AuthzDriver(conf['driver'], conf.get('url')) + ) diff --git a/moon_engine/moon_engine/orchestration_driver.py b/moon_engine/moon_engine/orchestration_driver.py new file mode 100644 index 00000000..85ca436d --- /dev/null +++ b/moon_engine/moon_engine/orchestration_driver.py @@ -0,0 +1,100 @@ +# 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. + + +import logging +from moon_engine.api import configuration +from moon_engine.api.orchestration import pipeline + +logger = logging.getLogger("moon.engine.orchestration_driver") + + +PipelineManager = None + + +class Driver: + + def __init__(self, driver_name, engine_name): + self.name = driver_name + self.plug = configuration.get_orchestration_driver() + self.driver = self.plug.Connector(driver_name, engine_name) + + +class PipelineDriver(Driver): + + def __init__(self, driver_name, engine_name): + super(PipelineDriver, self).__init__(driver_name, engine_name) + self.engine = engine_name + + def update_pipeline(self, pipeline_id, data): + """Update a pipeline + + :param pipeline_id: the ID of the pipeline + :param data: a dictionary { + "name": "the name of the pipeline", + "description": "the description of the pipeline", + "wrapper": {"url": "http://127.0.0.1:20000"}, + # "wrapper": {"url": "local"} # if the pipeline should be configured inside the wrapper + "plugins": ["moon_engine.plugins.authz", ] # the first plugin is the default + } + :return: the pipeline updated + """ + raise NotImplementedError() # pragma: no cover + + def delete_pipeline(self, pipeline_id): + """Delete the pipeline + + :param pipeline_id: the ID of the pipeline + :return: True if the pipeline has been deleted + """ + raise NotImplementedError() # pragma: no cover + + def add_pipeline(self, pipeline_id=None, data=None): + """Create a new pipeline + + :param pipeline_id: (optional) the ID of the pipeline to create + :param data: a dictionary { + "name": "the name of the pipeline", + "description": "the description of the pipeline", + "wrapper": {"url": "http://127.0.0.1:20000"}, + # "wrapper": {"url": "local"} # if the pipeline should be configured inside the wrapper + "plugins": ["moon_engine.plugins.authz", ] # the first plugin is the default + } + :return: the pipeline created + """ + raise NotImplementedError() # pragma: no cover + + def get_pipelines(self, pipeline_id=None): + """List one or more pipelines + + :param pipeline_id: (optional) the ID of the pipeline to list + :return: a list of one or more pipelines + """ + raise NotImplementedError() # pragma: no cover + + def get_pipeline_api_key(self, pipeline_id): + """Returns the api key of the pipeline with id pipeline_id + + :param pipeline_id: the ID of the pipeline to list + :return: The api key of the pipeline + """ + raise NotImplementedError() # pragma: no cover + + +def init(): + global PipelineManager + + logger.info("Initializing driver") + conf = configuration.get_configuration("orchestration") + + PipelineManager = pipeline.PipelineManager( + PipelineDriver(conf['driver'], conf.get('url')) + ) diff --git a/moon_engine/moon_engine/plugins/__init__.py b/moon_engine/moon_engine/plugins/__init__.py new file mode 100644 index 00000000..1856aa2c --- /dev/null +++ b/moon_engine/moon_engine/plugins/__init__.py @@ -0,0 +1,12 @@ +# 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. + + diff --git a/moon_engine/moon_engine/plugins/authz.py b/moon_engine/moon_engine/plugins/authz.py new file mode 100644 index 00000000..e4a9c662 --- /dev/null +++ b/moon_engine/moon_engine/plugins/authz.py @@ -0,0 +1,32 @@ +# 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. + + +import logging +from moon_engine.authz_driver import AuthzDriver + +PLUGIN_TYPE = "authz" +LOGGER = logging.getLogger("moon.engine.plugins.authz") + + +class AuthzConnector(AuthzDriver): + + def __init__(self, driver_name, engine_name): + self.driver_name = driver_name + self.engine_name = engine_name + + def get_authz(self, subject_name, object_name, action_name): + # FIXME: must add the real authorization engine here + return True + + +class Connector(AuthzConnector): + pass diff --git a/moon_engine/moon_engine/plugins/oslowrapper.py b/moon_engine/moon_engine/plugins/oslowrapper.py new file mode 100644 index 00000000..020a648a --- /dev/null +++ b/moon_engine/moon_engine/plugins/oslowrapper.py @@ -0,0 +1,137 @@ +# 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. + + +""" +Authz is the endpoint to get authorization response +""" + +import logging +import json +import hug +import requests +from moon_utilities import exceptions +from moon_cache.cache import Cache +from moon_engine.api.configuration import get_configuration + +PLUGIN_TYPE = "wrapper_api" +LOGGER = logging.getLogger("moon.wrapper.api." + __name__) + + +class OsloWrapper(object): + """ + Endpoint for authz requests + """ + + def __init__(self, **kwargs): + self.TIMEOUT = 5 + + @staticmethod + @hug.local() + @hug.post("/authz/oslo", output=hug.output_format.text) + def post(body, response): + LOGGER.debug("POST {}".format(body)) + response.status = hug.HTTP_400 + response_data = "False" + response.set_header('content-type', 'application/octet-stream') + try: + oslo_wrapper_checker = OsloWrapperChecker() + if oslo_wrapper_checker.manage_data(body): + response.status = hug.HTTP_200 + response_data = "True" + except exceptions.AuthzException as exception: + LOGGER.error(exception, exc_info=True) + except Exception as exception: + LOGGER.error(exception, exc_info=True) + + return response_data + + +class OsloWrapperChecker(object): + + def __init__(self): + self.CACHE = Cache.getInstance() + + @staticmethod + def __get_subject(target, credentials): + # FIXME: we should use the ID instead of the name + _subject = target.get("target.user.name", "") + if not _subject and credentials: + _subject = credentials.get("token", {}).get("user", {}).get( + "name", + credentials.get("user_id", "none")) + if not _subject: + _subject = target.get("user_id", "") + return _subject + + @staticmethod + def __get_object(target, credentials): + try: + # note: case of Glance + return target['target']['name'] + except KeyError: + pass + + # note: default case + return "all" + + @staticmethod + def __get_project_id(target, credentials): + project_id = credentials.get("project_id") + LOGGER.info("project_id {}".format(project_id)) + return project_id + + def manage_data(self, body): + data = body + if not dict(body): + data = json.loads(body.decode("utf-8")) + try: + target = json.loads(data.get('target', {})) + except TypeError: + target = data.get('target', {}) + try: + credentials = json.loads(data.get('credentials', {})) + except TypeError: + credentials = data.get('credentials', {}) + rule = data.get('rule', "") + _subject = self.__get_subject(target, credentials) + _object = self.__get_object(target, credentials) + _action = rule.strip('"') + _project_id = self.__get_project_id(target, credentials) + + host_url = self.CACHE.get_pipeline_url(project_id=_project_id) + if not host_url: + if get_configuration("grant_if_unknown_project"): + LOGGER.info("No interface found for {}, " + "granted anyway : grant_if_unknown_project is true in the conf file".format(_project_id)) + return True + LOGGER.error("No interface found for {}".format(_project_id)) + else: + LOGGER.debug("interface_url={}".format(host_url)) + _url = "{}/authz/{}/{}/{}".format( + host_url, + _subject, + _object, + _action + ) + LOGGER.debug("url={}".format(_url)) + req = requests.get(_url, timeout=2) + + if req.status_code == 204: + LOGGER.info("The request has been granted") + return True + LOGGER.debug("authz request: {} {}".format(req.status_code, req.content)) + raise exceptions.AuthzException("error in authz request") + + +def get_apis(): + yield OsloWrapper + diff --git a/moon_engine/moon_engine/plugins/pyorchestrator.py b/moon_engine/moon_engine/plugins/pyorchestrator.py new file mode 100644 index 00000000..bf2d70f9 --- /dev/null +++ b/moon_engine/moon_engine/plugins/pyorchestrator.py @@ -0,0 +1,372 @@ +# 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. + + +import logging +import os +import time +import requests +import subprocess # nosec +from uuid import uuid4 +import yaml +from moon_engine.orchestration_driver import PipelineDriver +from moon_engine.api import configuration +from moon_engine.api.configuration import get_configuration +from moon_engine import get_api_key +from moon_utilities.auth_functions import xor_decode +from moon_utilities import exceptions +from datetime import datetime + +LOGGER = logging.getLogger("moon.engine.orchestrator.driver.pyorchestrator") + +PLUGIN_TYPE = "orchestration" +pipelines = {} +ports = [] + + +def init(): + """Initialize the plugin by initializing wrappers + + :return: nothing + """ + + # FIXME: get pipelines from Manager + pass + + +def create_gunicorn_config(host, port, server_type, uuid): + """Create a Gunicorn config file in a temporary directory + + :return: filename + """ + config_dir = get_configuration("orchestration").get("config_dir", "/tmp") # nosec + # (/tmp is a fallback solution) + _log_config = get_configuration("logging") + _log_config["handlers"]["file"]["filename"] = os.path.join(config_dir, + "moon_{}.log".format(uuid)) + __manager_url = get_configuration("management")["url"] + filename = os.path.join(config_dir, "gunicorn_{}.cfg".format(uuid4().hex)) + fd = open(filename, "w") + fd.write("""bind = "{host}:{port}" +workers = {workers} +moon = "{moon_filename}" + """.format( + host=host, + port=port, + workers=1, + moon_filename=os.path.join(config_dir, "moon_{}.yaml".format(uuid)), + )) + fd.close() + return filename + + +def create_moon_config(uuid, manager_cnx=True, policy_file=None): + """Create a Moon config file in a temporary directory + + :return: filename + """ + LOGGER.info(f"create_moon_config({uuid})") + config_dir = get_configuration("orchestration").get("config_dir", "/tmp") # nosec + _log_config = get_configuration("logging") + _log_config["handlers"]["file"]["filename"] = os.path.join(config_dir, + "moon_{}.log".format(uuid)) + if manager_cnx: + __manager_url = get_configuration("management")["url"] + api_token = get_api_key(get_configuration("management")["url"], + get_configuration("management")["user"], + get_configuration("management")["password"]) + else: + __manager_url = "" + api_token = "" + config_dir = get_configuration("orchestration").get("config_dir", "/tmp") # nosec + # (/tmp is a fallback solution) + filename = os.path.join(config_dir, "moon_{}.yaml".format(uuid)) + config_dict = { + "type": "pipeline", + "uuid": uuid, + "management": { + "url": __manager_url, + "token_file": os.path.join(config_dir, "db_{}.json".format(uuid)) + }, + "incremental_updates": True, + "api_token": api_token, + "data": "", + "logging": _log_config, + "authorization": get_configuration("authorization", + {"driver": "moon_engine.plugins.authz"}), + "plugins": get_configuration("plugins"), + "debug": get_configuration(key='debug', default=False) + } + if policy_file: + config_dict['data'] = policy_file + if not manager_cnx: + config_dict['uuid'] = "" + config_dict['incremental_updates'] = False + LOGGER.info("Writing config file to {}".format(filename)) + yaml.dump(config_dict, open(filename, "w"), default_flow_style=False) + return filename + + +def kill_server(uuid): + """Kill the server given its UUID + + :param uuid: UUID of the server + :return: nothing + """ + LOGGER.info("pipelines={}".format(pipelines)) + if uuid in pipelines: + LOGGER.info("pipeline={}".format(pipelines[uuid])) + # Fixme: if the server has been restarted, the process attribute is empty + LOGGER.info("Killing server {} after {} of uptime".format( + uuid, + str(datetime.now() - datetime.fromtimestamp(pipelines[uuid]["starttime"])) + )) + with open(pipelines[uuid]["process"], 'r') as pid_file: + try: + pid = int(pid_file.read()) + except ValueError: + LOGGER.error("The pid found in {} is not valid".format(pipelines[uuid]["process"])) + return + + os.kill(pid, 15) + del_server_port(pipelines[uuid]["port"]) + pipelines.pop(uuid) + else: + LOGGER.warning("Cannot find UUID {} in wrappers or interfaces".format(uuid)) + + +def get_ports_range(): + ports_range = get_configuration("orchestration")["port"] + return int(ports_range.split(".")[0]), int(ports_range.split(".")[-1]) + + +def get_next_port(server_host="127.0.0.1"): + port_min, port_max = get_ports_range() + _port = port_min + _ports = [] + for _pipeline in pipelines: + _ports.append(pipelines[_pipeline]["port"]) + _ports.sort() + if not _ports: + _port = port_min + elif _ports[-1]+1 > port_max: + raise Exception( + "Cannot add a new slave because " + "the port range is bounded to {}".format(port_max)) + while True: + if _port in _ports: + _port += 1 + continue + try: + requests.get("http://{}:{}/status".format(server_host, _port), timeout=1) + except requests.exceptions.ConnectionError: + break + if _port > port_max: + raise Exception( + "Cannot add a new pipeline because " + "the port range is bounded to {}".format(port_max)) + _port += 1 + return _port + + +def add_server_port(port): + ports.append(port) + + +def del_server_port(port): + try: + ports.remove(port) + except ValueError: + LOGGER.warning("port {} is not in the known port".format(port)) + + +def get_server_url(uuid=None): + if not uuid: + return + url = "" + try: + if uuid in pipelines: + url = "http://{}:{}".format(pipelines[uuid]["server_ip"], + pipelines[uuid]["port"]) + if url: + response = requests.get(url + "/status") + if response.status_code == 200: + return url + except TimeoutError: + LOGGER.warning("A timeout occurred when connecting to {}".format(url)) + # if port has not be found in local data, try to get information from remote servers + port_min, port_max = get_ports_range() + host = "127.0.0.1" + for _port in range(port_min, port_max): + try: + req = requests.get("http://{}:{}/status".format(host, _port), timeout=1) + data = req.json() + if "status" in data and data["status"]["uuid"] == uuid: + return "http://{}:{}".format(host, _port) + except Exception as e: + LOGGER.warning("Error getting information from {} ({})".format(host, str(e))) + return + + +def start_new_server(uuid): + """Start a new server in a new process + + :param uuid: UUID of the server + :return: nothing + """ + _url = get_server_url(uuid) + config_dir = get_configuration("orchestration").get("config_dir", "/tmp") # nosec + server_ip = "127.0.0.1" + config_filename = os.path.join(config_dir, "moon_{}.yaml".format(uuid)) + LOGGER.info("Starting server {} {}".format(_url, uuid)) + if _url: + _port = int(_url.split(":")[-1]) + add_server_port(_port) + config = yaml.safe_load(open(config_filename)) + log_file = config["logging"]["handlers"]["file"]["filename"] + _out = { + "pipeline_id": uuid, + "starttime": time.time(), + "port": _port, + "host": server_ip, + "server_ip": server_ip, + "log_file": log_file + } + else: + _port = get_next_port() + create_moon_config(uuid=uuid) + pid_file = os.path.join(config_dir, uuid + ".pid") + # NOTE: we have actually no solution to get the actual IP address + # so we need to put 0.0.0.0 in the host address + gunicorn_config = create_gunicorn_config( + host="0.0.0.0", # nosec + port=_port, + server_type="pipeline", + uuid=uuid) + command = ["gunicorn", "moon_engine.server:__hug_wsgi__", "--threads", "10", + "-p", pid_file, "-D", "-c", gunicorn_config] + LOGGER.info("Executing {}".format(" ".join(command))) + subprocess.Popen(command, stdout=subprocess.PIPE, close_fds=True) # nosec + # (command attribute is safe) + _out = { + "pipeline_id": uuid, + "starttime": time.time(), + "port": _port, + "host": server_ip, + "server_ip": server_ip, + "process": pid_file, + } + time.sleep(1) + config = yaml.safe_load(open(config_filename)) + log_file = config["logging"]["handlers"]["file"]["filename"] + _out["log"] = log_file + for cpt in range(10): + try: + f_sock = open(log_file) + except FileNotFoundError: + time.sleep(1) + else: + break + else: + LOGGER.error("Cannot find log file ({})".format(log_file)) + return + p_sock = 0 + LOGGER.info("Process running") + while True: + f_sock.seek(p_sock) + latest_data = f_sock.read() + p_sock = f_sock.tell() + if latest_data and "APIKEY" in latest_data: + _index_start = latest_data.index("APIKEY=") + len("APIKEY=") + _index_stop = latest_data.index("\n", _index_start) + key = latest_data[_index_start:_index_stop].strip() + # api_key = get_api_key_for_user("admin") + api_key = configuration.get_configuration('api_token') + try: + engine_api_key = xor_decode(key, api_key) + except exceptions.DecryptError: + engine_api_key = False + _out["api_key"] = engine_api_key + break + time.sleep(1) + + return _out + + +class PipelineConnector(PipelineDriver): + + def __init__(self, driver_name, engine_name): + self.driver_name = driver_name + self.engine_name = engine_name + + def update_pipeline(self, pipeline_id, data): + _url = get_server_url(pipeline_id) + if not _url: + self.add_pipeline(pipeline_id, data) + if "security_pipeline" in data: + req = requests.post("{}/update".format(_url), json={"attributes": "pdp"}) + if req.status_code == 206: + LOGGER.warning("No pipeline available...") + elif req.status_code != 202: + LOGGER.warning("Error sending upgrade command to pipeline ({})".format(req)) + if "vim_project_id" in data and data['vim_project_id']: + LOGGER.warning("Cannot update vim_project_id for the moment") + # FIXME: manage vim_project_id + + def delete_pipeline(self, pipeline_id): + LOGGER.info("Deleting pipeline {}".format(pipeline_id)) + kill_server(pipeline_id) + + def add_pipeline(self, pipeline_id=None, data=None): + LOGGER.debug("Adding POD in Engine {} {}".format(pipeline_id, data)) + if not pipeline_id: + pipeline_id = uuid4().hex + if not data: + content = dict() + else: + content = dict(data) + content.update(start_new_server(pipeline_id)) + pipelines[pipeline_id] = content + return pipelines[pipeline_id] + + def get_pipelines(self, pipeline_id=None): + results = {} + for interface in pipelines: + results[interface] = { + "starttime": pipelines[interface]["starttime"], + "port": pipelines[interface]["port"], + "server_ip": pipelines[interface]["server_ip"], + "status": "down", + "log": pipelines[interface]["log"] + } + try: + req = requests.get("http://{}:{}/status".format( + pipelines[interface]["server_ip"], + pipelines[interface]["port"] + )) + if req.status_code == 200: + results[interface]["status"] = "up" + except TimeoutError: + LOGGER.warning("Timeout connecting {} on port {}".format( + pipelines[interface]["server_ip"], + pipelines[interface]["port"] + )) + return results + + def get_pipeline_api_key(self, pipeline_id): + return pipelines.get(pipeline_id, {}).get('api_key', "") + + +class Connector(PipelineConnector): + pass + + +init() 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 + )) + + |