# The following ENV can be added with "docker run -e xxx' to configure
# server
-ENV OSMNBI_SOCKET_HOST 0.0.0.0
-ENV OSMNBI_SOCKET_PORT 9999
+ENV OSMNBI_SOCKET_HOST 0.0.0.0
+ENV OSMNBI_SOCKET_PORT 9999
# storage
-ENV OSMNBI_STORAGE_PATH /app/storage
+ENV OSMNBI_STORAGE_PATH /app/storage
# database
-ENV OSMNBI_DATABASE_DRIVER mongo
-ENV OSMNBI_DATABASE_HOST mongo
-ENV OSMNBI_DATABASE_PORT 27017
+ENV OSMNBI_DATABASE_DRIVER mongo
+ENV OSMNBI_DATABASE_HOST mongo
+ENV OSMNBI_DATABASE_PORT 27017
# web
-ENV OSMNBI_STATIC_DIR /app/osm_nbi/html_public
+ENV OSMNBI_STATIC_DIR /app/osm_nbi/html_public
# logs
-ENV OSMNBI_LOG_FILE /app/log
-ENV OSMNBI_LOG_LEVEL DEBUG
+ENV OSMNBI_LOG_FILE /app/log
+ENV OSMNBI_LOG_LEVEL DEBUG
# message
-ENV OSMNBI_MESSAGE_DRIVER kafka
-ENV OSMNBI_MESSAGE_HOST kafka
-ENV OSMNBI_MESSAGE_PORT 9092
+ENV OSMNBI_MESSAGE_DRIVER kafka
+ENV OSMNBI_MESSAGE_HOST kafka
+ENV OSMNBI_MESSAGE_PORT 9092
# logs
-ENV OSMNBI_LOG_FILE /app/log/nbi.log
-ENV OSMNBI_LOG_LEVEL DEBUG
+ENV OSMNBI_LOG_FILE /app/log/nbi.log
+ENV OSMNBI_LOG_LEVEL DEBUG
+# authenticator
+ENV OSMNBI_AUTHENTICATOR_DB_HOST mysql
+ENV OSMNBI_AUTHENTICATOR_DB_PORT 3306
+ENV OSMNBI_AUTHENTICATOR_DB_ROOT_PASSWORD mysql
+ENV OSMNBI_AUTHENTICATOR_DB_USER mysql
+ENV OSMNBI_AUTHENTICATOR_DB_PASS mysql
+ENV OSMNBI_AUTHENTICATOR_BACKEND keystone
+ENV OSMNBI_AUTHENTICATOR_AUTH_URL keystone
+ENV OSMNBI_AUTHENTICATOR_USER_DOMAIN_NAME keystone
+ENV OSMNBI_AUTHENTICATOR_PROJECT_DOMAIN_NAME keystone
+ENV OSMNBI_AUTHENTICATOR_SERVICE_USERNAME authenticator
+ENV OSMNBI_AUTHENTICATOR_SERVICE_PASSWORD authenticator
+ENV OSMNBI_AUTHENTICATOR_SERVICE_PROJECT admin
# Run app.py when the container launches
CMD ["python3", "nbi.py"]
+# -*- coding: utf-8 -*-
+
+"""
+Authenticator is responsible for authenticating the users,
+create the tokens unscoped and scoped, retrieve the role
+list inside the projects that they are inserted
+"""
+
+__author__ = "Eduardo Sousa <eduardosousa@av.it.pt>"
+__date__ = "$27-jul-2018 23:59:59$"
+
+import logging
+
import cherrypy
from base64 import standard_b64decode
from http import HTTPStatus
-
+from authconn_keystone import AuthconnKeystone
from engine import EngineException
-__author__ = "Eduardo Sousa <eduardosousa@av.it.pt>"
-
-class AuthenticatorException(Exception):
+class AuthException(Exception):
def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED):
self.http_code = http_code
Exception.__init__(self, message)
-class Authenticator(object):
+class Authenticator:
+ """
+ This class should hold all the mechanisms for User Authentication and
+ Authorization. Initially it should support Openstack Keystone as a
+ backend through a plugin model where more backends can be added and a
+ RBAC model to manage permissions on operations.
+ """
+
def __init__(self, engine):
+ """
+ Authenticator initializer. Setup the initial state of the object,
+ while it waits for the config dictionary and database initialization.
+
+ Note: engine is only here until all the calls can to it can be replaced.
+
+ :param engine: reference to engine object used.
+ """
super().__init__()
self.engine = engine
+ self.backend = None
+ self.config = None
+ self.db = None
+ self.logger = logging.getLogger("nbi.authenticator")
+
+ def start(self, config):
+ """
+ Method to configure the Authenticator object. This method should be called
+ after object creation. It is responsible by initializing the selected backend,
+ as well as the initialization of the database connection.
+
+ :param config: dictionary containing the relevant parameters for this object.
+ """
+ self.config = config
+
+ try:
+ if not self.backend:
+ if config["authenticator"]["backend"] == "keystone":
+ self.backend = AuthconnKeystone(self.config["authenticator"])
+ if not self.db:
+ pass
+ # TODO: Implement database initialization
+ # NOTE: Database needed to store the mappings
+ except Exception as e:
+ raise AuthException(str(e))
+
+ pass
+
+ def init_db(self, target_version='1.0'):
+ """
+ Check if the database has been initialized. If not, create the required tables
+ and insert the predefined mappings between roles and permissions.
+
+ :param target_version: schema version that should be present in the database.
+ :return: None if OK, exception if error or version is different.
+ """
+ pass
+
def authorize(self):
token = None
user_passwd64 = None
if cherrypy.session.get('Authorization'):
del cherrypy.session['Authorization']
cherrypy.response.headers["WWW-Authenticate"] = 'Bearer realm="{}"'.format(e)
- raise AuthenticatorException(str(e))
+ raise AuthException(str(e))
def new_token(self, session, indata, remote):
return self.engine.new_token(session, indata, remote)
--- /dev/null
+# -*- coding: utf-8 -*-
+
+"""
+Authconn implements an Abstract class for the Auth backend connector
+plugins with the definition of the methods to be implemented.
+"""
+
+__author__ = "Eduardo Sousa <eduardosousa@av.it.pt>"
+__date__ = "$27-jul-2018 23:59:59$"
+
+from http import HTTPStatus
+
+
+class AuthconnException(Exception):
+ """
+ Common and base class Exception for all authconn exceptions.
+ """
+ def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED):
+ Exception.__init__(message)
+ self.http_code = http_code
+
+
+class AuthconnConnectionException(AuthconnException):
+ """
+ Connectivity error with Auth backend.
+ """
+ def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED):
+ AuthconnException.__init__(self, message, http_code)
+
+
+class AuthconnNotSupportedException(AuthconnException):
+ """
+ The request is not supported by the Auth backend.
+ """
+ def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED):
+ AuthconnException.__init__(self, message, http_code)
+
+
+class AuthconnNotImplementedException(AuthconnException):
+ """
+ The method is not implemented by the Auth backend.
+ """
+ def __init__(self, message, http_code=HTTPStatus.UNAUTHORIZED):
+ AuthconnException.__init__(self, message, http_code)
+
+
+class Authconn:
+ """
+ Abstract base class for all the Auth backend connector plugins.
+ Each Auth backend connector plugin must be a subclass of
+ Authconn class.
+ """
+ def __init__(self, config):
+ """
+ Constructor of the Authconn class.
+
+ Note: each subclass
+
+ :param config: configuration dictionary containing all the
+ necessary configuration parameters.
+ """
+ self.config = config
+
+ def authenticate_with_user_password(self, user, password):
+ """
+ Authenticate a user using username and password.
+
+ :param user: username
+ :param password: password
+ :return: an unscoped token that grants access to project list
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def authenticate_with_token(self, token, project=None):
+ """
+ Authenticate a user using a token. Can be used to revalidate the token
+ or to get a scoped token.
+
+ :param token: a valid token.
+ :param project: (optional) project for a scoped token.
+ :return: return a revalidated token, scoped if a project was passed or
+ the previous token was already scoped.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def validate_token(self, token):
+ """
+ Check if the token is valid.
+
+ :param token: token to validate
+ :return: dictionary with information associated with the token. If the
+ token is not valid, returns None.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def revoke_token(self, token):
+ """
+ Invalidate a token.
+
+ :param token: token to be revoked
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def get_project_list(self, token):
+ """
+ Get all the projects associated with a user.
+
+ :param token: valid token
+ :return: list of projects
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def get_role_list(self, token):
+ """
+ Get role list for a scoped project.
+
+ :param token: scoped token.
+ :return: returns the list of roles for the user in that project. If
+ the token is unscoped it returns None.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def create_user(self, user, password):
+ """
+ Create a user.
+
+ :param user: username.
+ :param password: password.
+ :return: boolean to indicate if operation was successful.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def change_password(self, user, old_password, new_password):
+ """
+ Change the user password.
+
+ :param user: username.
+ :param old_password: old password.
+ :param new_password: new password.
+ :return: boolean to indicate if operation was successful.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def delete_user(self, user):
+ """
+ Delete user.
+
+ :param user: username.
+ :return: boolean to indicate if operation was successful.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def create_role(self, role):
+ """
+ Create a role.
+
+ :param role: role name.
+ :return: boolean to indicate if operation was successful.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def delete_role(self, role):
+ """
+ Delete a role.
+
+ :param role: role name.
+ :return: boolean to indicate if operation was successful.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def create_project(self, project):
+ """
+ Create a project.
+
+ :param project: project name.
+ :return: boolean to indicate if operation was successful.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def delete_project(self, project):
+ """
+ Delete a project.
+
+ :param project: project name.
+ :return: boolean to indicate if operation was successful.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
+
+ def assign_role_to_user(self, user, project, role):
+ """
+ Assigning a role to a user in a project.
+
+ :param user: username.
+ :param project: project name.
+ :param role: role name.
+ :return: boolean to indicate if operation was successful.
+ """
+ raise AuthconnNotImplementedException("Should have implemented this")
--- /dev/null
+# -*- coding: utf-8 -*-
+
+"""
+AuthconnKeystone implements implements the connector for
+Openstack Keystone and leverages the RBAC model, to bring
+it for OSM.
+"""
+
+__author__ = "Eduardo Sousa <eduardosousa@av.it.pt>"
+__date__ = "$27-jul-2018 23:59:59$"
+
+from authconn import Authconn
+
+
+class AuthconnKeystone(Authconn):
+ def __init__(self, config):
+ Authconn.__init__(self, config)
import getopt
import sys
-from auth import Authenticator
+from auth import Authenticator, AuthException
from engine import Engine, EngineException
from osm_common.dbbase import DbException
from osm_common.fsbase import FsException
__version__ = "0.1.3"
version_date = "Apr 2018"
database_version = '1.0'
+auth_database_version = '1.0'
"""
North Bound Interface (O: OSM specific; 5,X: SOL005 not implemented yet; O5: SOL005 implemented)
update_dict['server.socket_host'] = v
elif k1 in ("server", "test", "auth", "log"):
update_dict[k1 + '.' + k2] = v
- elif k1 in ("message", "database", "storage"):
+ elif k1 in ("message", "database", "storage", "authenticator"):
# k2 = k2.replace('_', '.')
- if k2 == "port":
+ if k2 in ("port", "db_port"):
engine_config[k1][k2] = int(v)
else:
engine_config[k1][k2] = v
+
except ValueError as e:
cherrypy.log.error("Ignoring environ '{}': " + str(e))
except Exception as e:
logger_module.setLevel(engine_config[k1]["loglevel"])
# TODO add more entries, e.g.: storage
cherrypy.tree.apps['/osm'].root.engine.start(engine_config)
+ cherrypy.tree.apps['/osm'].root.authenticator.start(engine_config)
try:
cherrypy.tree.apps['/osm'].root.engine.init_db(target_version=database_version)
- except EngineException:
+ cherrypy.tree.apps['/osm'].root.authenticator.init_db(target_version=auth_database_version)
+ except (EngineException, AuthException):
pass
# getenv('OSMOPENMANO_TENANT', None)