From 17690987fe470a962164ec168dc805db8a511130 Mon Sep 17 00:00:00 2001 From: Frank Sandoval Date: Wed, 24 Oct 2018 02:15:50 -0600 Subject: Enforce AAF permissions Issue-ID: OPTFRA-331 Change-Id: I046ddef243f73ae90ca0a28184ee0decf73069ee Signed-off-by: Frank Sandoval Signed-off-by: Dileep Ranganathan --- .../api/adapters/aaf/aaf_authentication.py | 118 ++++++++++++++------- conductor/conductor/api/controllers/v1/plans.py | 6 +- conductor/conductor/common/sms.py | 3 + conductor/conductor/opts.py | 2 +- 4 files changed, 86 insertions(+), 43 deletions(-) (limited to 'conductor') diff --git a/conductor/conductor/api/adapters/aaf/aaf_authentication.py b/conductor/conductor/api/adapters/aaf/aaf_authentication.py index e6b79d2..f669ba4 100644 --- a/conductor/conductor/api/adapters/aaf/aaf_authentication.py +++ b/conductor/conductor/api/adapters/aaf/aaf_authentication.py @@ -20,9 +20,11 @@ import base64 from datetime import datetime, timedelta import json +import os from conductor.common import rest from conductor.i18n import _LE, _LI +from conductor import __file__ as conductor_root from oslo_log import log LOG = log.getLogger(__name__) @@ -30,32 +32,46 @@ LOG = log.getLogger(__name__) from oslo_config import cfg CONF = cfg.CONF -# TBD - read values from conductor.conf AAF_OPTS = [ cfg.BoolOpt('is_aaf_enabled', - default=True, + default=False, help='is_aaf_enabled.'), cfg.IntOpt('aaf_cache_expiry_hrs', - default='3', + default='24', help='aaf_cache_expiry_hrs.'), cfg.StrOpt('aaf_url', - default='http://aaf-service:8100/authz/perms/user/', + default='https://aaf-service:8100/authz/perms/user/', help='aaf_url.'), + cfg.StrOpt('username', + default=None, + help='username.'), + cfg.StrOpt('password', + default=None, + help='pasword.'), + cfg.StrOpt('aaf_cert_file', + default=None, + help='aaf_cert_file.'), + cfg.StrOpt('aaf_cert_key_file', + default=None, + help='aaf_cert_key_file.'), + cfg.StrOpt('aaf_ca_bundle_file', + default="", + help='aaf_ca_bundle_file.'), cfg.IntOpt('aaf_retries', default='3', help='aaf_retries.'), cfg.IntOpt('aaf_timeout', default='100', help='aaf_timeout.'), - cfg.ListOpt('aaf_user_roles', - default=['{"type": "org.onap.oof","instance": "plans","action": "GET"}', - '{"type": "org.onap.oof","instance": "plans","action": "POST"}'], + cfg.StrOpt('aaf_conductor_user', + default=None, + help='aaf_conductor_user.'), + cfg.ListOpt('aaf_permissions', + default=['{"type": "org.onap.oof.access","instance": "*","action": "*"}'], help='aaf_user_roles.') ] -CONF.register_opts(AAF_OPTS, group='aaf_authentication') - -AUTHZ_PERMS_USER = '{}/authz/perms/user/{}' +CONF.register_opts(AAF_OPTS, group='aaf_api') EXPIRE_TIME = 'expire_time' @@ -64,11 +80,22 @@ perm_cache = {} def clear_cache(): perm_cache.clear() - def authenticate(uid, passwd): + # FS - trace + LOG.info("Authenticating username:password {} : {}: ".format(uid, passwd)) + + aafUser = None + username = CONF.conductor_api.username + password = CONF.conductor_api.password + if username == uid and password == passwd: + aafUser = CONF.aaf_api.aaf_conductor_user + else: + LOG.debug("Error Authenticating the user {} : {}: ".format(uid, passwd)) + return False + try: - perms = get_aaf_permissions(uid, passwd) - return has_valid_role(perms) + perms = get_aaf_permissions(aafUser) + return has_valid_permissions(perms) except Exception as exp: LOG.error("Error Authenticating the user {} : {}: ".format(uid, exp)) pass @@ -80,22 +107,27 @@ return True if the user has valid permissions else return false """ -def has_valid_role(perms): - aaf_user_roles = CONF.aaf_authentication.aaf_user_roles - - permObj = json.loads(perms) - permList = permObj["perm"] - for user_role in aaf_user_roles: - role = json.loads(user_role) - userType = role["type"] - userInstance = role["instance"] - userAction = role["action"] - for perm in permList: - permType = perm["type"] - permInstance = perm["instance"] - permAction = perm["action"] +def has_valid_permissions(userPerms): + permissions = CONF.aaf_api.aaf_permissions + + LOG.info("Validate permisions: acquired permissions {} ".format(userPerms)) + LOG.info("Validate permisions: allowed permissions {} ".format(permissions)) + + userPermObj = json.loads(userPerms) + userPermList = userPermObj["perm"] + for perm in permissions: + permObj = json.loads(perm) + permType = permObj["type"] + permInstance = permObj["instance"] + permAction = permObj["action"] + for userPerm in userPermList: + userType = userPerm["type"] + userInstance = userPerm["instance"] + userAction = userPerm["action"] if userType == permType and userInstance == permInstance and \ (userAction == permAction or userAction == "*"): + # FS - trace + LOG.info("User has valid permissions ") return True return False @@ -104,35 +136,43 @@ Make the remote aaf api call if user is not in the cache. Return the perms """ -def get_aaf_permissions(uid, passwd): - key = base64.b64encode("{}_{}".format(uid, passwd), "ascii") - time_delta = timedelta(hours = CONF.aaf_authentication.aaf_cache_expiry_hrs) +def get_aaf_permissions(aafUser): + key = base64.b64encode("{}".format(aafUser), "ascii") + time_delta = timedelta(hours = CONF.aaf_api.aaf_cache_expiry_hrs) -# TBD - test cache logic perms = perm_cache.get(key) if perms and datetime.now() < perms.get(EXPIRE_TIME): LOG.debug("Returning cached value") return perms['roles'] LOG.debug("Invoking AAF authentication API") - response = remote_api(passwd, uid) + response = remote_api(aafUser) perms = {EXPIRE_TIME: datetime.now() + time_delta, 'roles': response} perm_cache[key] = perms return response -def remote_api(passwd, uid): - server_url = CONF.aaf_authentication.aaf_url.rstrip('/') + +""" +The remote api is the AAF service + +""" +def remote_api(aafUser): + server_url = CONF.aaf_api.aaf_url+aafUser + kwargs = { "server_url": server_url, - "retries": CONF.aaf_authentication.aaf_retries, - "username": uid, - "password": passwd, + "retries": CONF.aaf_api.aaf_retries, + "username": CONF.aaf_api.username, + "password": CONF.aaf_api.password, "log_debug": LOG.debug, - "read_timeout": CONF.aaf_authentication.aaf_timeout, + "read_timeout": CONF.aaf_api.aaf_timeout, + "cert_file": CONF.aaf_api.aaf_cert_file, + "cert_key_file": CONF.aaf_api.aaf_cert_key_file, + "ca_bundle_file": CONF.aaf_api.aaf_ca_bundle_file, } restReq = rest.REST(**kwargs) - headers = {"Accept": "application/json"} + headers = {"Accept": "application/Perms+json;q=1.0;charset=utf-8;version=2.1,application/json;q=1.0;version=2.1,*/*;q=1.0"} rkwargs = { "method": 'GET', "path": '', diff --git a/conductor/conductor/api/controllers/v1/plans.py b/conductor/conductor/api/controllers/v1/plans.py index b32caef..411686b 100644 --- a/conductor/conductor/api/controllers/v1/plans.py +++ b/conductor/conductor/api/controllers/v1/plans.py @@ -85,7 +85,7 @@ class PlansBaseController(object): def plans_get(self, plan_id=None): - auth_flag = CONF.conductor_api.basic_auth_secure or CONF.aaf_authentication.is_aaf_enabled + auth_flag = CONF.conductor_api.basic_auth_secure or CONF.aaf_api.is_aaf_enabled # TBD - is healthcheck properly supported? if plan_id == 'healthcheck' or \ @@ -291,7 +291,7 @@ class PlansController(PlansBaseController): if args and args['name']: LOG.info('Plan name: {}'.format(args['name'])) - auth_flag = CONF.conductor_api.basic_auth_secure or CONF.aaf_authentication.is_aaf_enabled + auth_flag = CONF.conductor_api.basic_auth_secure or CONF.aaf_api.is_aaf_enabled # Create the plan only when the basic authentication is disabled or pass the authenticaiton check if not auth_flag or \ @@ -360,7 +360,7 @@ def verify_user(authstr): retVal = False - if CONF.aaf_authentication.is_aaf_enabled: + if CONF.aaf_api.is_aaf_enabled: retVal = aaf_auth.authenticate(user_dict['username'], user_dict['password']) else: if username == user_dict['username'] and password == user_dict['password']: diff --git a/conductor/conductor/common/sms.py b/conductor/conductor/common/sms.py index c5eee3a..6e21392 100644 --- a/conductor/conductor/common/sms.py +++ b/conductor/conductor/common/sms.py @@ -111,6 +111,9 @@ def load_secrets(): config.set_override('aafns', secret_dict['music_api']['aafns'], 'music_api') config.set_override('username', secret_dict['sdnc']['username'], 'sdnc') config.set_override('password', secret_dict['sdnc']['password'], 'sdnc') + config.set_override('username', secret_dict['aaf_api']['username'], 'aaf_api') + config.set_override('password', secret_dict['aaf_api']['password'], 'aaf_api') + config.set_override('aaf_conductor_user', secret_dict['aaf_api']['aaf_conductor_user'], 'aaf_api') def delete_secrets(): diff --git a/conductor/conductor/opts.py b/conductor/conductor/opts.py index 106de2d..d711a4e 100644 --- a/conductor/conductor/opts.py +++ b/conductor/conductor/opts.py @@ -75,7 +75,7 @@ def list_opts(): ('solver', conductor.solver.service.SOLVER_OPTS), ('reservation', conductor.reservation.service.reservation_OPTS), ('aaf_sms', conductor.common.sms.AAF_SMS_OPTS), - ('aaf_authentication', + ('aaf_api', conductor.api.adapters.aaf.aaf_authentication.AAF_OPTS), ('prometheus', conductor.common.prometheus_metrics.METRICS_OPTS), ] -- cgit 1.2.3-korg