aboutsummaryrefslogtreecommitdiffstats
path: root/moon_engine/moon_engine
diff options
context:
space:
mode:
Diffstat (limited to 'moon_engine/moon_engine')
-rw-r--r--moon_engine/moon_engine/__init__.py30
-rw-r--r--moon_engine/moon_engine/__main__.py113
-rw-r--r--moon_engine/moon_engine/api/__init__.py29
-rw-r--r--moon_engine/moon_engine/api/authz/__init__.py12
-rw-r--r--moon_engine/moon_engine/api/authz/authz.py28
-rw-r--r--moon_engine/moon_engine/api/authz/managers.py20
-rw-r--r--moon_engine/moon_engine/api/configuration.py195
-rw-r--r--moon_engine/moon_engine/api/import_json.py29
-rw-r--r--moon_engine/moon_engine/api/logs.py25
-rw-r--r--moon_engine/moon_engine/api/orchestration/__init__.py12
-rw-r--r--moon_engine/moon_engine/api/orchestration/managers.py21
-rw-r--r--moon_engine/moon_engine/api/orchestration/pipeline.py51
-rw-r--r--moon_engine/moon_engine/api/orchestration/slave.py44
-rw-r--r--moon_engine/moon_engine/api/pipeline/__init__.py11
-rw-r--r--moon_engine/moon_engine/api/pipeline/authz.py35
-rw-r--r--moon_engine/moon_engine/api/pipeline/update.py188
-rw-r--r--moon_engine/moon_engine/api/pipeline/update_pipeline.py220
-rw-r--r--moon_engine/moon_engine/api/pipeline/validator.py125
-rw-r--r--moon_engine/moon_engine/api/status.py30
-rw-r--r--moon_engine/moon_engine/api/wrapper/__init__.py11
-rw-r--r--moon_engine/moon_engine/api/wrapper/api/__init__.py11
-rw-r--r--moon_engine/moon_engine/api/wrapper/api/authz.py43
-rw-r--r--moon_engine/moon_engine/api/wrapper/api/pipeline.py100
-rw-r--r--moon_engine/moon_engine/api/wrapper/api/update.py179
-rw-r--r--moon_engine/moon_engine/api/wrapper/router.py115
-rw-r--r--moon_engine/moon_engine/api/wrapper/update_wrapper.py233
-rw-r--r--moon_engine/moon_engine/authz_driver.py68
-rw-r--r--moon_engine/moon_engine/orchestration_driver.py100
-rw-r--r--moon_engine/moon_engine/plugins/__init__.py12
-rw-r--r--moon_engine/moon_engine/plugins/authz.py32
-rw-r--r--moon_engine/moon_engine/plugins/oslowrapper.py137
-rw-r--r--moon_engine/moon_engine/plugins/pyorchestrator.py372
-rw-r--r--moon_engine/moon_engine/server.py288
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
+ ))
+
+