aboutsummaryrefslogtreecommitdiffstats
path: root/moon_engine/moon_engine/server.py
blob: 0b0f28b264e199006676f87507175d483cab8235 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
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
    ))