diff --git a/src/api-lib/vnc_api.py b/src/api-lib/vnc_api.py index bde61b0ac92..b29bc61e825 100644 --- a/src/api-lib/vnc_api.py +++ b/src/api-lib/vnc_api.py @@ -22,6 +22,7 @@ from gen.resource_client import * from gen.generatedssuper import GeneratedsSuper +import cfgm_common from cfgm_common import rest, utils from cfgm_common.exceptions import * from cfgm_common import ssl_adapter @@ -1196,9 +1197,11 @@ def set_multi_tenancy(self, enabled): content = self._request_server(rest.OP_PUT, url, json.dumps(data)) return json.loads(content) - def set_multi_tenancy_with_rbac(self, enabled): - url = self._action_uri['rbac'] - data = {'enabled': enabled} + def set_aaa_mode(self, mode): + if mode not in cfgm_common.AAA_MODE_VALID_VALUES: + raise HttpError(400, 'Invalid AAA mode') + url = self._action_uri['aaa-mode'] + data = {'aaa-mode': mode} content = self._request_server(rest.OP_PUT, url, json.dumps(data)) return json.loads(content) diff --git a/src/config/api-server/tests/test_crud_basic.py b/src/config/api-server/tests/test_crud_basic.py index b409d40f4df..9360411c715 100644 --- a/src/config/api-server/tests/test_crud_basic.py +++ b/src/config/api-server/tests/test_crud_basic.py @@ -1163,6 +1163,7 @@ def test_name_with_reserved_xml_char(self): # end test_name_with_reserved_xml_char def test_list_bulk_collection(self): + self.skipTest("Skipping test_list_bulk_collection for separate testcase") obj_count = self._vnc_lib.POST_FOR_LIST_THRESHOLD + 1 vn_uuids = [] ri_uuids = [] @@ -2080,7 +2081,7 @@ def fake_static_file(*args, **kwargs): self.assertThat(resp.status_code, Equals(200)) logger.info("Negative case without Documentation") - url = 'http://%s:%s/' %(listen_ip, listen_port) + url = 'http://%s:%s/virtual-networks' %(listen_ip, listen_port) orig_rbac_role = TestLocalAuth._rbac_role try: TestLocalAuth._rbac_role = 'foobar' diff --git a/src/config/api-server/tests/test_perms2.py b/src/config/api-server/tests/test_perms2.py index b4db54606ec..47617860371 100644 --- a/src/config/api-server/tests/test_perms2.py +++ b/src/config/api-server/tests/test_perms2.py @@ -25,12 +25,14 @@ import inspect import requests import stevedore +import bottle from vnc_api.vnc_api import * import keystoneclient.exceptions as kc_exceptions import keystoneclient.v2_0.client as keystone from keystonemiddleware import auth_token from cfgm_common import rest, utils +from cfgm_common.rbaclib import * import cfgm_common sys.path.append('../common/tests') @@ -41,6 +43,7 @@ logger = logging.getLogger(__name__) logger.setLevel(logging.DEBUG) +from vnc_api.gen.resource_xsd import PermType, PermType2, IdPermsType PERMS_NONE = 0 PERMS_X = 1 PERMS_W = 2 @@ -82,10 +85,11 @@ def __init__(self, apis_ip, apis_port, kc, name, password, role, project): if tenant.name == self.project: break self.project_uuid = tenant.id + self.tenant = tenant if self.name not in kc_users: logger.info( 'user %s missing from keystone ... creating' % self.name) - user = kc.users.create(self.name, self.password, '', tenant_id=tenant.id) + kc.users.create(self.name, self.password, '', tenant_id=tenant.id) role_dict = {role.name:role for role in kc.roles.list()} user_dict = {user.name:user for user in kc.users.list()} @@ -126,7 +130,7 @@ def set_perms(obj, owner=None, owner_access=None, share=None, global_access=None try: perms = obj.get_perms2() except AttributeError: - logger.info( '*** Unable to set perms2 in object %s' % obj.get_fq_name()) + logger.info( 'Unable to set perms2 in object %s' % obj.get_fq_name()) sys.exit() logger.info( 'Current perms %s = %s' % (obj.get_fq_name(), print_perms(perms))) @@ -147,11 +151,11 @@ def set_perms(obj, owner=None, owner_access=None, share=None, global_access=None # end set_perms # Read VNC object. Return None if object doesn't exists -def vnc_read_obj(vnc, obj_type, name = None, obj_uuid = None): +def vnc_read_obj(vnc, res_type, name = None, obj_uuid = None): if name is None and obj_uuid is None: - logger.info( 'Need FQN or UUID to read object') + logger.info('Need FQN or UUID to read object') return None - method_name = obj_type.replace('-', '_') + method_name = res_type.replace('-', '_') method = getattr(vnc, "%s_read" % (method_name)) try: if obj_uuid: @@ -161,116 +165,38 @@ def vnc_read_obj(vnc, obj_type, name = None, obj_uuid = None): else: return method(fq_name=name) except NoIdError: - logger.info( '%s %s not found!' % (obj_type, name if name else obj_uuid)) + logger.info( '%s %s not found!' % (res_type, name if name else obj_uuid)) return None except PermissionDenied: - logger.info( 'Permission denied reading %s %s' % (obj_type, name)) + logger.info( 'Permission denied reading %s %s' % (res_type, name)) raise # end -def show_rbac_rules(api_access_list_entries): - if api_access_list_entries is None: - logger.info( 'Empty RBAC group!') - return - - # {u'rbac_rule': [{u'rule_object': u'*', u'rule_perms': [{u'role_crud': u'CRUD', u'role_name': u'admin'}], u'rule_field': None}]} - rule_list = api_access_list_entries.get_rbac_rule() - logger.info( 'Rules (%d):' % len(rule_list)) - logger.info( '----------') - idx = 1 - for rule in rule_list: - o = rule.rule_object - f = rule.rule_field - ps = '' - for p in rule.rule_perms: - ps += p.role_name + ':' + p.role_crud + ',' - o_f = "%s.%s" % (o,f) if f else o - logger.info( '%2d %-32s %s' % (idx, o_f, ps)) - idx += 1 - logger.info( '') - -def build_rule(rule_str): - r = rule_str.split(" ") if rule_str else [] - if len(r) < 2: - return None - - # [0] is object.field, [1] is list of perms - obj_field = r[0].split(".") - perms = r[1].split(",") - - o = obj_field[0] - f = obj_field[1] if len(obj_field) > 1 else None - o_f = "%s.%s" % (o,f) if f else o - - # perms eg ['foo:CRU', 'bar:CR'] - rule_perms = [] - for perm in perms: - p = perm.split(":") - rule_perms.append(RbacPermType(role_name = p[0], role_crud = p[1])) - - # build rule - rule = RbacRuleType( - rule_object = o, - rule_field = f, - rule_perms = rule_perms) - return rule -#end - -def match_rule(rule_list, rule_str): - extend_rule_list = True - nr = build_rule(rule_str) - for r in rule_list: - if r.rule_object != nr.rule_object or r.rule_field != nr.rule_field: - continue - - # object and field match - fix rule in place - extend_rule_list = False - - for np in nr.rule_perms: - extend_perms = True - for op in r.rule_perms: - if op.role_name == np.role_name: - # role found - merge incoming and existing crud in place - x = set(list(op.role_crud)) | set(list(np.role_crud)) - op.role_crud = ''.join(x) - extend_perms = False - if extend_perms: - r.rule_perms.append(RbacPermType(role_name = np.role_name, role_crud = np.role_crud)) - - if extend_rule_list: - rule_list.append(nr) - -# end match_rule - -def vnc_fix_api_access_list(vnc_lib, pobj, rule_str = None): +def vnc_aal_create(vnc, pobj): rg_name = list(pobj.get_fq_name()) rg_name.append('default-api-access-list') - - rg = vnc_read_obj(vnc_lib, 'api-access-list', name = rg_name) - - create = False - rule_list = [] + rg = vnc_read_obj(vnc, 'api-access-list', name = rg_name) if rg == None: + rge = RbacRuleEntriesType([]) rg = ApiAccessList( name = 'default-api-access-list', parent_obj = pobj, - api_access_list_entries = None) - create = True - elif rule_str: - api_access_list_entries = rg.get_api_access_list_entries() - rule_list = api_access_list_entries.get_rbac_rule() - - if rule_str: - rule = match_rule(rule_list, rule_str) - - rentry = RbacRuleEntriesType(rule_list) - rg.set_api_access_list_entries(rentry) - if create: - logger.info( 'API access list empty. Creating with default rule') - vnc_lib.api_access_list_create(rg) + api_access_list_entries = rge) + vnc.api_access_list_create(rg) + return rg + +def vnc_aal_add_rule(vnc, rg, rule_str): + rule = build_rule(rule_str) + rg = vnc_read_obj(vnc, 'api-access-list', rg.get_fq_name()) + rge = rg.get_api_access_list_entries() + match = find_rule(rge, rule) + if not match: + rge.add_rbac_rule(rule) else: - vnc_lib.api_access_list_update(rg) - show_rbac_rules(rg.get_api_access_list_entries()) + build_perms(rge.rbac_rule[match[0]-1], match[3]) + + rg.set_api_access_list_entries(rge) + vnc.api_access_list_update(rg) def token_from_user_info(user_name, tenant_name, domain_name, role_name, tenant_id = None): @@ -283,7 +209,7 @@ def token_from_user_info(user_name, tenant_name, domain_name, role_name, 'X-Role': role_name, } rval = json.dumps(token_dict) - # logger.info( '**** Generate token %s ****' % rval) + # logger.info( 'Generate token %s' % rval) return rval class MyVncApi(VncApi): @@ -309,7 +235,7 @@ def _authenticate(self, response=None, headers=None): # This is needed for VncApi._authenticate invocation from within Api server. # We don't have access to user information so we hard code admin credentials. def ks_admin_authenticate(self, response=None, headers=None): - rval = token_from_user_info('admin', 'admin', 'default-domain', 'admin') + rval = token_from_user_info('admin', 'admin', 'default-domain', 'cloud-admin') new_headers = {} new_headers['X-AUTH-TOKEN'] = rval return new_headers @@ -317,7 +243,6 @@ def ks_admin_authenticate(self, response=None, headers=None): class TestPermissions(test_case.ApiServerTestCase): domain_name = 'default-domain' fqdn = [domain_name] - vn_name='alice-vn' @classmethod def setUpClass(cls): @@ -328,12 +253,14 @@ def setUpClass(cls): (auth_token, 'AuthProtocol', test_utils.FakeAuthProtocol)] extra_config_knobs = [ - ('DEFAULTS', 'multi_tenancy_with_rbac', 'True'), + ('DEFAULTS', 'aaa_mode', 'rbac'), + ('DEFAULTS', 'cloud_admin_role', 'cloud-admin'), ('DEFAULTS', 'auth', 'keystone'), ] super(TestPermissions, cls).setUpClass(extra_mocks=extra_mocks, extra_config_knobs=extra_config_knobs) + def setUp(self): super(TestPermissions, self).setUp() ip = self._api_server_ip @@ -344,49 +271,112 @@ def setUp(self): auth_url='http://127.0.0.1:5000/v2.0') # prepare token before vnc api invokes keystone - alice = User(ip, port, kc, 'alice', 'alice123', 'alice-role', 'alice-proj-%s' % self.id()) - bob = User(ip, port, kc, 'bob', 'bob123', 'bob-role', 'bob-proj-%s' % self.id()) - admin = User(ip, port, kc, 'admin', 'contrail123', 'admin', 'admin-%s' % self.id()) + self.alice = User(ip, port, kc, 'alice', 'alice123', 'alice-role', 'alice-proj-%s' % self.id()) + self.bob = User(ip, port, kc, 'bob', 'bob123', 'bob-role', 'bob-proj-%s' % self.id()) + self.admin = User(ip, port, kc, 'admin', 'contrail123', 'cloud-admin', 'admin-%s' % self.id()) + self.admin1 = User(ip, port, kc, 'admin1', 'contrail123', 'admin', 'admin1-%s' % self.id()) + self.admin2 = User(ip, port, kc, 'admin2', 'contrail123', 'admin', 'admin2-%s' % self.id()) - self.alice = alice - self.bob = bob - self.admin = admin - self.users = [self.alice, self.bob] + self.users = [self.alice, self.bob, self.admin1, self.admin2] """ 1. create project in API server 2. read objects back and pupolate locally 3. reassign ownership of projects to user from admin """ - for user in [admin, alice, bob]: + for user in [self.admin, self.alice, self.bob, self.admin1, self.admin2]: project_obj = Project(user.project) project_obj.uuid = user.project_uuid logger.info( 'Creating Project object for %s, uuid %s' \ % (user.project, user.project_uuid)) - admin.vnc_lib.project_create(project_obj) + self.admin.vnc_lib.project_create(project_obj) # read projects back - user.project_obj = vnc_read_obj(admin.vnc_lib, + user.project_obj = vnc_read_obj(self.admin.vnc_lib, 'project', obj_uuid = user.project_uuid) logger.info( 'Change owner of project %s to %s' % (user.project, user.project_uuid)) set_perms(user.project_obj, owner=user.project_uuid, share = []) - admin.vnc_lib.project_update(user.project_obj) + self.admin.vnc_lib.project_update(user.project_obj) - # delete test VN if it exists - vn_fq_name = [self.domain_name, alice.project, self.vn_name] - vn = vnc_read_obj(admin.vnc_lib, 'virtual-network', name = vn_fq_name) - if vn: - logger.info( '%s exists ... deleting to start fresh' % vn_fq_name) - admin.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + # allow permission to create objects (including obj-perms) + for user in self.users: + logger.info( "%s: project %s to allow full access to role %s" % \ + (user.name, user.project, user.role)) + user.proj_rg = vnc_aal_create(self.admin.vnc_lib, user.project_obj) + vnc_aal_add_rule(self.admin.vnc_lib, user.proj_rg, + rule_str = '* %s:CRUD' % user.role) - # allow permission to create objects + """ + global_rg = vnc_read_obj(self.admin.vnc_lib, 'api-access-list', + name = ['default-global-system-config', 'default-api-access-list']) + vnc_aal_add_rule(self.admin.vnc_lib, global_rg, "obj-perms *:R") + """ + + def test_delete_non_admin_role(self): + alice = self.alice + bob = self.bob + admin = self.admin + self.vn_name = "alice-vn-%s" % self.id() + + # allow permission to create all objects for user in self.users: logger.info( "%s: project %s to allow full access to role %s" % \ (user.name, user.project, user.role)) - vnc_fix_api_access_list(self.admin.vnc_lib, user.project_obj, + vnc_aal_add_rule(self.admin.vnc_lib, user.proj_rg, rule_str = '* %s:CRUD' % user.role) + vn_fq_name = [self.domain_name, alice.project, self.vn_name] + + # delete test VN if it exists + if vnc_read_obj(admin.vnc_lib, 'virtual-network', name = vn_fq_name): + logger.info( '%s exists ... deleting to start fresh' % vn_fq_name) + admin.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + + vn = VirtualNetwork(self.vn_name, self.alice.project_obj) + self.alice.vnc_lib.virtual_network_create(vn) + + # bob - delete VN ... should fail + try: + bob.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + self.assertTrue(False, 'Bob Deleted VN ... test failed!') + except PermissionDenied as e: + self.assertTrue(True, 'Error deleting VN ... test passed!') + + # Alice - delete VN ... should succeed + try: + alice.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + self.assertTrue(True, 'Deleted VN ... test succeeded!') + except PermissionDenied as e: + self.assertTrue(False, 'Alice Error deleting VN ... test failed!') + # end + + def test_delete_admin_role(self): + vn = VirtualNetwork('admin1-vn', self.admin1.project_obj) + vn_fq_name = vn.get_fq_name() + + # delete test VN if it exists + if vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name): + logger.info( '%s exists ... deleting to start fresh' % vn_fq_name) + self.admin.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + + self.admin1.vnc_lib.virtual_network_create(vn) + + # admin2 - delete VN ... should fail + try: + self.admin2.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + self.assertTrue(False, 'Deleted VN ... test failed!') + except PermissionDenied as e: + self.assertTrue(True, 'Error deleting VN ... test passed!') + + # admin1 - delete VN ... should succeed + try: + self.admin1.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + self.assertTrue(True, 'Deleted VN ... test succeeded!') + except PermissionDenied as e: + self.assertTrue(False, 'Error deleting VN ... test failed!') + # end + # delete api-access-list for alice and bob and disallow api access to their projects # then try to create VN in the project. This should fail def test_api_access(self): @@ -396,23 +386,24 @@ def test_api_access(self): alice = self.alice bob = self.bob admin = self.admin + self.vn_name = "alice-vn-%s" % self.id() - rv_json = admin.vnc_lib._request(rest.OP_GET, '/multi-tenancy-with-rbac') + rv_json = admin.vnc_lib._request(rest.OP_GET, '/aaa-mode') rv = json.loads(rv_json) - self.assertEquals(rv["enabled"], True) - - # disable rbac + self.assertEquals(rv["aaa-mode"], "rbac") # delete api-access-list for alice and bob and disallow api access to their projects for user in self.users: logger.info( "Delete api-acl for project %s to disallow api access" % user.project) - vnc_fix_api_access_list(self.admin.vnc_lib, user.project_obj, rule_str = None) + rg_name = list(user.project_obj.get_fq_name()) + rg_name.append('default-api-access-list') + self.admin.vnc_lib.api_access_list_delete(fq_name = rg_name) logger.info( 'alice: trying to create VN in her project') vn = VirtualNetwork(self.vn_name, self.alice.project_obj) try: self.alice.vnc_lib.virtual_network_create(vn) - self.assertTrue(False, '*** Created virtual network ... test failed!') + self.assertTrue(False, 'Created virtual network ... test failed!') except PermissionDenied as e: self.assertTrue(True, 'Failed to create VN ... Test passes!') @@ -421,7 +412,8 @@ def test_api_access(self): logger.info( "%s: project %s to allow full access to role %s" % \ (user.name, user.project, user.role)) # note that collection API is set for create operation - vnc_fix_api_access_list(self.admin.vnc_lib, user.project_obj, + user.proj_rg = vnc_aal_create(self.admin.vnc_lib, user.project_obj) + vnc_aal_add_rule(self.admin.vnc_lib, user.proj_rg, rule_str = 'virtual-networks %s:C' % user.role) logger.info( '') @@ -441,12 +433,12 @@ def test_api_access(self): try: vn_fq_name = [self.domain_name, self.alice.project, self.vn_name] vn = vnc_read_obj(self.alice.vnc_lib, 'virtual-network', name = vn_fq_name) - self.assertTrue(False, '*** Read VN without read permission') + self.assertTrue(False, 'Read VN without read permission') except PermissionDenied as e: self.assertTrue(True, 'Unable to read VN ... test passed') # allow read access - vnc_fix_api_access_list(self.admin.vnc_lib, self.alice.project_obj, + vnc_aal_add_rule(self.admin.vnc_lib, self.alice.proj_rg, rule_str = 'virtual-network %s:R' % self.alice.role) logger.info( 'alice: added permission to read virtual-network') logger.info( 'alice: trying to read VN in her project (should succeed)') @@ -454,7 +446,7 @@ def test_api_access(self): vn = vnc_read_obj(self.alice.vnc_lib, 'virtual-network', name = vn_fq_name) self.assertTrue(True, 'Read VN successfully ... test passed') except PermissionDenied as e: - self.assertTrue(False, '*** Read VN failed ... test failed!!!') + self.assertTrue(False, 'Read VN failed ... test failed!!!') logger.info('') logger.info( '########### API ACCESS (UPDATE) ##################') @@ -462,13 +454,13 @@ def test_api_access(self): try: vn.display_name = "foobar" alice.vnc_lib.virtual_network_update(vn) - self.assertTrue(False, '*** Set field in VN ... test failed!') + self.assertTrue(False, 'Set field in VN ... test failed!') testfail += 1 except PermissionDenied as e: self.assertTrue(True, 'Unable to update field in VN ... Test succeeded!') # give update API access to alice - vnc_fix_api_access_list(admin.vnc_lib, alice.project_obj, + vnc_aal_add_rule(admin.vnc_lib, alice.proj_rg, rule_str = 'virtual-network %s:U' % alice.role) logger.info( '') @@ -479,7 +471,7 @@ def test_api_access(self): alice.vnc_lib.virtual_network_update(vn) self.assertTrue(True, 'Set field in VN ... test passed!') except PermissionDenied as e: - self.assertTrue(False, '*** Failed to update field in VN ... Test failed!') + self.assertTrue(False, 'Failed to update field in VN ... Test failed!') testfail += 1 if testfail > 0: sys.exit() @@ -491,12 +483,12 @@ def test_api_access(self): logger.info('') logger.info( '####### API ACCESS (update field restricted to admin) ##############') logger.info( 'Restricting update of field to admin only ') - vnc_fix_api_access_list(admin.vnc_lib, alice.project_obj, + vnc_aal_add_rule(admin.vnc_lib, alice.proj_rg, rule_str = 'virtual-network.display_name admin:U') try: vn.display_name = "alice" alice.vnc_lib.virtual_network_update(vn) - self.assertTrue(False, '*** Set field in VN ... test failed!') + self.assertTrue(False, 'Set field in VN ... test failed!') except PermissionDenied as e: self.assertTrue(True, 'Failed to update field in VN ... Test passed!') @@ -508,7 +500,7 @@ def test_api_access(self): vn_fq_name = [self.domain_name, alice.project, self.vn_name] try: alice.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) - self.assertTrue(False, '*** Deleted VN ... test failed!') + self.assertTrue(False, 'Deleted VN ... test failed!') except PermissionDenied as e: self.assertTrue(True, 'Error deleting VN ... test passed!') @@ -517,7 +509,7 @@ def test_api_access(self): logger.info( 'Giving bob API level access to perform all ops on virtual-network') logger.info( "Bob should'nt be able to create VN in alice project because only\n ") logger.info( 'owner (alice) has write permission in her project') - vnc_fix_api_access_list(admin.vnc_lib, bob.project_obj, + vnc_aal_add_rule(admin.vnc_lib, bob.proj_rg, rule_str = 'virtual-network %s:CRUD' % bob.role) logger.info( '') @@ -525,7 +517,7 @@ def test_api_access(self): try: vn2 = VirtualNetwork('bob-vn-in-alice-project', alice.project_obj) bob.vnc_lib.virtual_network_create(vn2) - self.assertTrue(False, '*** Created virtual network ... test failed!') + self.assertTrue(False, 'Created virtual network ... test failed!') except PermissionDenied as e: self.assertTrue(True, 'Failed to create VN ... Test passed!') @@ -540,7 +532,7 @@ def test_api_access(self): logger.info( 'Reading VN as bob ... should fail') try: net_obj = bob.vnc_lib.virtual_network_read(id=vn.get_uuid()) - self.assertTrue(False, '*** Succeeded in reading VN. Test failed!') + self.assertTrue(False, 'Succeeded in reading VN. Test failed!') except PermissionDenied as e: self.assertTrue(True, 'Failed to read VN ... Test passed!') @@ -553,7 +545,7 @@ def test_api_access(self): net_obj = bob.vnc_lib.virtual_network_read(id=vn.get_uuid()) self.assertTrue(True, 'Succeeded in reading VN. Test passed!') except PermissionDenied as e: - self.assertTrue(False, '*** Failed to read VN ... Test failed!') + self.assertTrue(False, 'Failed to read VN ... Test failed!') logger.info('') logger.info( '########### READ (DISABLE READ SHARING) ##################') @@ -566,7 +558,7 @@ def test_api_access(self): net_obj = bob.vnc_lib.virtual_network_read(id=vn.get_uuid()) self.assertTrue(False, 'Succeeded in reading VN. Test failed!') except PermissionDenied as e: - self.assertTrue(True, '*** Failed to read VN ... Test passed!') + self.assertTrue(True, 'Failed to read VN ... Test passed!') logger.info('') logger.info( '########### READ (GLOBALLY SHARED) ##################') @@ -579,14 +571,14 @@ def test_api_access(self): net_obj = bob.vnc_lib.virtual_network_read(id=vn.get_uuid()) self.assertTrue(True, 'Succeeded in reading VN. Test passed!') except PermissionDenied as e: - self.assertTrue(False, '*** Failed to read VN ... Test failed!') + self.assertTrue(False, 'Failed to read VN ... Test failed!') logger.info( '########### WRITE (GLOBALLY SHARED) ##################') logger.info( 'Writing shared VN as bob ... should fail') try: vn.display_name = "foobar" bob.vnc_lib.virtual_network_update(vn) - self.assertTrue(False, '*** Succeeded in updating VN. Test failed!!') + self.assertTrue(False, 'Succeeded in updating VN. Test failed!!') except PermissionDenied as e: self.assertTrue(True, 'Failed to update VN ... Test passed!') @@ -602,7 +594,7 @@ def test_api_access(self): bob.vnc_lib.virtual_network_update(vn) self.assertTrue(True, 'Succeeded in updating VN. Test passed!') except PermissionDenied as e: - self.assertTrue(False, '*** Failed to update VN ... Test failed!!') + self.assertTrue(False, 'Failed to update VN ... Test failed!!') logger.info( '') logger.info( '########################### COLLECTIONS #################') @@ -612,7 +604,7 @@ def test_api_access(self): try: x = alice.vnc_lib.virtual_networks_list(parent_id = alice.project_uuid) self.assertTrue(False, - '*** Read VN collection without list permission ... test failed!') + 'Read VN collection without list permission ... test failed!') except PermissionDenied as e: self.assertTrue(True, 'Failed to read VN collection ... test passed') @@ -621,7 +613,7 @@ def test_api_access(self): logger.info( "%s: project %s to allow collection access to role %s" % \ (user.name, user.project, user.role)) # note that collection API is set for create operation - vnc_fix_api_access_list(admin.vnc_lib, user.project_obj, + vnc_aal_add_rule(admin.vnc_lib, user.proj_rg, rule_str = 'virtual-networks %s:CR' % user.role) # create one more VN in alice project to differentiate from what bob sees @@ -633,7 +625,7 @@ def test_api_access(self): x = alice.vnc_lib.virtual_networks_list(parent_id = alice.project_uuid) for item in x['virtual-networks']: logger.info( ' %s: %s' % (item['uuid'], item['fq_name'])) - expected = set(['alice-vn', 'second-vn']) + expected = set([self.vn_name, 'second-vn']) received = set([item['fq_name'][-1] for item in x['virtual-networks']]) self.assertEquals(expected, received) @@ -643,7 +635,7 @@ def test_api_access(self): for item in y['virtual-networks']: logger.info( ' %s: %s' % (item['uuid'], item['fq_name'])) # need changes in auto code generation for lists - expected = set(['alice-vn']) + expected = set([self.vn_name]) received = set([item['fq_name'][-1] for item in y['virtual-networks']]) self.assertEquals(expected, received) @@ -655,14 +647,15 @@ def test_check_obj_perms_api(self): alice = self.alice bob = self.bob admin = self.admin + self.vn_name = "alice-vn-%s" % self.id() # allow permission to create virtual-network for user in self.users: logger.info( "%s: project %s to allow full access to role %s" % \ (user.name, user.project, user.role)) # note that collection API is set for create operation - vnc_fix_api_access_list(self.admin.vnc_lib, user.project_obj, - rule_str = 'virtual-networks %s:CRUD' % user.role) + vnc_aal_add_rule(self.admin.vnc_lib, user.proj_rg, + "virtual-networks %s:CRUD" % user.role) logger.info( '') logger.info( 'alice: trying to create VN in her project') @@ -677,7 +670,7 @@ def test_check_obj_perms_api(self): self.assertThat(testfail, Equals(False)) ExpectedPerms = {'admin':'RWX', 'alice':'RWX', 'bob':''} - for user in self.users: + for user in [alice, bob, admin]: perms = user.check_perms(vn.get_uuid()) self.assertEquals(perms, ExpectedPerms[user.name]) @@ -688,7 +681,7 @@ def test_check_obj_perms_api(self): alice.vnc_lib.virtual_network_update(vn) ExpectedPerms = {'admin':'RWX', 'alice':'RWX', 'bob':'R'} - for user in self.users: + for user in [alice, bob, admin]: perms = user.check_perms(vn.get_uuid()) self.assertEquals(perms, ExpectedPerms[user.name]) @@ -699,7 +692,7 @@ def test_check_obj_perms_api(self): alice.vnc_lib.virtual_network_update(vn) ExpectedPerms = {'admin':'RWX', 'alice':'RWX', 'bob':''} - for user in self.users: + for user in [alice, bob, admin]: perms = user.check_perms(vn.get_uuid()) self.assertEquals(perms, ExpectedPerms[user.name]) logger.info( 'Reading VN as bob ... should fail') @@ -711,7 +704,7 @@ def test_check_obj_perms_api(self): alice.vnc_lib.virtual_network_update(vn) ExpectedPerms = {'admin':'RWX', 'alice':'RWX', 'bob':'R'} - for user in self.users: + for user in [alice, bob, admin]: perms = user.check_perms(vn.get_uuid()) self.assertEquals(perms, ExpectedPerms[user.name]) logger.info( 'Reading VN as bob ... should fail') @@ -723,7 +716,7 @@ def test_check_obj_perms_api(self): alice.vnc_lib.virtual_network_update(vn) ExpectedPerms = {'admin':'RWX', 'alice':'RWX', 'bob':'RW'} - for user in self.users: + for user in [alice, bob, admin]: perms = user.check_perms(vn.get_uuid()) self.assertEquals(perms, ExpectedPerms[user.name]) @@ -737,13 +730,14 @@ def test_ri_owner(self): alice = self.alice bob = self.bob admin = self.admin + self.vn_name = "alice-vn-%s" % self.id() # allow permission to create virtual-network for user in self.users: logger.info( "%s: project %s to allow full access to role %s" % \ (user.name, user.project, user.role)) # note that collection API is set for create operation - vnc_fix_api_access_list(self.admin.vnc_lib, user.project_obj, + vnc_aal_add_rule(self.admin.vnc_lib, user.proj_rg, rule_str = 'virtual-networks %s:CRUD' % user.role) # Create VN as non-admin user @@ -767,6 +761,7 @@ def test_chown_api(self): alice = self.alice bob = self.bob admin = self.admin + self.vn_name = "alice-vn-%s" % self.id() logger.info( 'alice: create VN in her project') vn_fq_name = [self.domain_name, alice.project, self.vn_name] @@ -801,9 +796,9 @@ def test_chown_api(self): admin.vnc_lib.chown(vn.get_uuid(), alice.project_uuid) # ensure chown/chmod works even when rbac is disabled - self.assertRaises(PermissionDenied, alice.vnc_lib.set_multi_tenancy_with_rbac, False) - rv = admin.vnc_lib.set_multi_tenancy_with_rbac(False) - self.assertEquals(rv['enabled'], False) + self.assertRaises(PermissionDenied, alice.vnc_lib.set_aaa_mode, "cloud-admin") + rv = admin.vnc_lib.set_aaa_mode("cloud-admin") + self.assertEquals(rv['aaa-mode'], "cloud-admin") alice.vnc_lib.chown(vn.get_uuid(), valid_uuid_1) admin.vnc_lib.chown(vn.get_uuid(), alice.project_uuid) alice.vnc_lib.chmod(vn.get_uuid(), owner=valid_uuid_1) @@ -811,8 +806,8 @@ def test_chown_api(self): # re-enable rbac for subsequent tests! try: - rv = admin.vnc_lib.set_multi_tenancy_with_rbac(True) - self.assertEquals(rv['enabled'], True) + rv = admin.vnc_lib.set_aaa_mode("rbac") + self.assertEquals(rv['aaa-mode'], "rbac") except Exception: self.fail("Error in enabling rbac") @@ -827,6 +822,7 @@ def test_chmod_api(self): alice = self.alice bob = self.bob admin = self.admin + self.vn_name = "alice-vn-%s" % self.id() logger.info( 'alice: create VN in her project') vn_fq_name = [self.domain_name, alice.project, self.vn_name] @@ -865,6 +861,13 @@ def test_chmod_api(self): perms = user.check_perms(vn.get_uuid()) self.assertEquals(perms, ExpectedPerms[user.name]) + logger.info( 'Enable virtual networks in alice project for global sharing (read, write, link)') + alice.vnc_lib.chmod(vn.get_uuid(), global_access=7) + ExpectedPerms = {'admin':'RWX', 'alice':'RWX', 'bob':'RWX'} + for user in [alice, bob, admin]: + perms = user.check_perms(vn.get_uuid()) + self.assertEquals(perms, ExpectedPerms[user.name]) + # negative test cases invalid_uuid = '7a574f27-6934-4970-C767-b4996bd30f36' valid_uuid_1 = '7a574f27-6934-4970-8767-b4996bd30f36' @@ -893,7 +896,142 @@ def test_chmod_api(self): alice.vnc_lib.chmod(vn.get_uuid(), owner=valid_uuid_2) admin.vnc_lib.chmod(vn.get_uuid(), owner=alice.project_uuid) + def test_bug_1604986(self): + """ + 1) Create a VN + 2) Make is globally shared + 3) list of virtual-networks should not return VN information twice + """ + admin = self.admin + vn_name = "test-vn-1604986" + vn_fq_name = [self.domain_name, admin.project, vn_name] + + test_vn = VirtualNetwork(vn_name, admin.project_obj) + self.admin.vnc_lib.virtual_network_create(test_vn) + + z = self.admin.vnc_lib.resource_list('virtual-network') + test_vn_list = [vn for vn in z['virtual-networks'] if vn['fq_name'][-1] == vn_name] + self.assertEquals(len(test_vn_list), 1) + + test_vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + set_perms(test_vn, global_access = PERMS_RWX) + admin.vnc_lib.virtual_network_update(test_vn) + + z = self.admin.vnc_lib.resource_list('virtual-network') + test_vn_list = [vn for vn in z['virtual-networks'] if vn['fq_name'][-1] == vn_name] + self.assertEquals(len(test_vn_list), 1) + + def test_shared_network(self): + alice = self.alice + bob = self.bob + admin = self.admin + self.vn_name = "alice-vn-%s" % self.id() + vn_fq_name = [self.domain_name, alice.project, self.vn_name] + + # create VN with 'is_shared' set - validate global_access set in vnc + vn = VirtualNetwork(self.vn_name, self.alice.project_obj) + vn.set_is_shared(True) + self.alice.vnc_lib.virtual_network_create(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_perms2().global_access, PERMS_RWX) + self.admin.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + + # create VN with global_access set - validate 'is_shared' gets set + vn = VirtualNetwork(self.vn_name, self.alice.project_obj) + perms = PermType2('cloud-admin', PERMS_RWX, PERMS_RWX, []) + vn.set_perms2(perms) + self.alice.vnc_lib.virtual_network_create(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_is_shared(), True) + self.admin.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + + # update VN 'is_shared' after initial create - ensure reflectd in global_access + vn = VirtualNetwork(self.vn_name, self.alice.project_obj) + self.alice.vnc_lib.virtual_network_create(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_perms2().global_access, 0) + vn.set_is_shared(True); self.alice.vnc_lib.virtual_network_update(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_perms2().global_access, PERMS_RWX) + vn.set_is_shared(False); self.alice.vnc_lib.virtual_network_update(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_perms2().global_access, 0) + self.admin.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + + # VN global_access is reset after initial create - ensure reflected in 'is_shared' + vn = VirtualNetwork(self.vn_name, self.alice.project_obj) + self.alice.vnc_lib.virtual_network_create(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_is_shared(), False or None) + perms = vn.get_perms2() + perms.global_access = PERMS_RWX + vn.set_perms2(perms); self.alice.vnc_lib.virtual_network_update(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_is_shared(), True) + perms = vn.get_perms2() + perms.global_access = 0 + vn.set_perms2(perms); self.alice.vnc_lib.virtual_network_update(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_is_shared(), False) + self.admin.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + + """ + # create VN with inconsistent global_access and is_shared set + vn = VirtualNetwork(self.vn_name, self.alice.project_obj) + perms = PermType2('cloud-admin', PERMS_RWX, PERMS_RWX, []) + vn.set_perms2(perms) + vn.set_is_shared(False); + with ExpectedException(BadRequest) as e: + self.alice.vnc_lib.virtual_network_create(vn) + perms.global_access = PERMS_NONE + vn.set_perms2(perms) + vn.set_is_shared(True); + with ExpectedException(BadRequest) as e: + self.alice.vnc_lib.virtual_network_create(vn) + """ + + # update VN with inconsistent global_access and is_shared set + vn = VirtualNetwork(self.vn_name, self.alice.project_obj) + perms = PermType2('cloud-admin', PERMS_RWX, PERMS_RWX, []) + vn.set_perms2(perms) + vn.set_is_shared(True); + self.alice.vnc_lib.virtual_network_create(vn) + vn = vnc_read_obj(self.admin.vnc_lib, 'virtual-network', name = vn_fq_name) + self.assertEquals(vn.get_is_shared(), True) + self.assertEquals(vn.get_perms2().global_access, PERMS_RWX) + perms.global_access = PERMS_NONE + vn.set_perms2(perms) + vn.set_is_shared(True); + with ExpectedException(BadRequest) as e: + self.alice.vnc_lib.virtual_network_update(vn) + perms.global_access = PERMS_RWX + vn.set_perms2(perms) + vn.set_is_shared(False); + with ExpectedException(BadRequest) as e: + self.alice.vnc_lib.virtual_network_update(vn) + # self.admin.vnc_lib.virtual_network_delete(fq_name = vn_fq_name) + + def test_doc_auth(self): + alice = self.alice + + # delete api-access-list for alice project + # disallow access to all API except globally allowed + rg_name = list(alice.project_obj.get_fq_name()) + rg_name.append('default-api-access-list') + self.admin.vnc_lib.api_access_list_delete(fq_name = rg_name) + + def fake_static_file(*args, **kwargs): + return + with test_common.patch(bottle, 'static_file', fake_static_file): + status_code, result = alice.vnc_lib._http_get('/documentation/index.html') + self.assertThat(status_code, Equals(200)) + + status_code, result = alice.vnc_lib._http_get('/') + self.assertThat(status_code, Equals(200)) + + status_code, result = alice.vnc_lib._http_get('/virtual-networks') + self.assertThat(status_code, Equals(401)) + def tearDown(self): super(TestPermissions, self).tearDown() # end tearDown - diff --git a/src/config/api-server/tests/test_rbac.py b/src/config/api-server/tests/test_rbac.py new file mode 100644 index 00000000000..4a6a000f663 --- /dev/null +++ b/src/config/api-server/tests/test_rbac.py @@ -0,0 +1,357 @@ +# +# Copyright (c) 2013 Juniper Networks, Inc. All rights reserved. +# +import gevent +import os +import sys +import socket +import errno +import uuid +import logging +import coverage + +import cgitb +cgitb.enable(format='text') + +import fixtures +import testtools +from testtools.matchers import Equals, MismatchError, Not, Contains +from testtools import content, content_type, ExpectedException +import unittest +import re +import json +import copy +from lxml import etree +import inspect +import requests +import stevedore + +from vnc_api.vnc_api import * +import keystoneclient.exceptions as kc_exceptions +import keystoneclient.v2_0.client as keystone +from keystonemiddleware import auth_token +from cfgm_common import rest, utils +import cfgm_common + +sys.path.append('../common/tests') +import test_utils +import test_common +import test_case + +logger = logging.getLogger(__name__) +logger.setLevel(logging.DEBUG) + +PERMS_NONE = 0 +PERMS_X = 1 +PERMS_W = 2 +PERMS_R = 4 +PERMS_WX = 3 +PERMS_RX = 5 +PERMS_RW = 6 +PERMS_RWX = 7 + +# create users specified as array of tuples (name, password, role) +# assumes admin user and tenant exists + +def normalize_uuid(id): + return id.replace('-','') + +class User(object): + def __init__(self, apis_ip, apis_port, kc, name, password, role, project): + self.name = name + self.password = password + self.role = role + self.project = project + self.project_uuid = None + self.project_obj = None + + # create user/role/tenant in keystone as needed + kc_users = set([user.name for user in kc.users.list()]) + kc_roles = set([user.name for user in kc.roles.list()]) + kc_tenants = set([tenant.name for tenant in kc.tenants.list()]) + + if self.role not in kc_roles: + logger.info('role %s missing from keystone ... creating' % self.role) + kc.roles.create(self.role) + + if self.project not in kc_tenants: + logger.info( 'tenant %s missing from keystone ... creating' % self.project) + kc.tenants.create(self.project) + + for tenant in kc.tenants.list(): + if tenant.name == self.project: + break + self.project_uuid = tenant.id + self.tenant = tenant + + if self.name not in kc_users: + logger.info( 'user %s missing from keystone ... creating' % self.name) + kc.users.create(self.name, self.password, '', tenant_id=tenant.id) + + role_dict = {role.name:role for role in kc.roles.list()} + user_dict = {user.name:user for user in kc.users.list()} + self.user = user_dict[self.name] + + # update tenant ID (needed if user entry already existed in keystone) + self.user.tenant_id = tenant.id + + logger.info( 'Adding user %s with role %s to tenant %s' \ + % (name, role, project)) + try: + kc.roles.add_user_role(user_dict[self.name], role_dict[self.role], tenant) + except kc_exceptions.Conflict: + pass + + self.vnc_lib = MyVncApi(username = self.name, password = self.password, + tenant_name = self.project, + api_server_host = apis_ip, api_server_port = apis_port) + # end __init__ + +def token_from_user_info(user_name, tenant_name, domain_name, role_name, + tenant_id = None): + token_dict = { + 'X-User': user_name, + 'X-User-Name': user_name, + 'X-Project-Name': tenant_name, + 'X-Project-Id': tenant_id or '', + 'X-Domain-Name' : domain_name, + 'X-Role': role_name, + } + rval = json.dumps(token_dict) + # logger.info( 'Generate token %s' % rval) + return rval + +class MyVncApi(VncApi): + def __init__(self, username = None, password = None, + tenant_name = None, api_server_host = None, api_server_port = None): + self._username = username + self._tenant_name = tenant_name + self.auth_token = None + self._kc = keystone.Client(username='admin', password='contrail123', + tenant_name='admin', + auth_url='http://127.0.0.1:5000/v2.0') + VncApi.__init__(self, username = username, password = password, + tenant_name = tenant_name, api_server_host = api_server_host, + api_server_port = api_server_port) + + def _authenticate(self, response=None, headers=None): + role_name = self._kc.user_role(self._username, self._tenant_name) + uobj = self._kc.users.get(self._username) + rval = token_from_user_info(self._username, self._tenant_name, + 'default-domain', role_name, uobj.tenant_id) + new_headers = headers or {} + new_headers['X-AUTH-TOKEN'] = rval + self.auth_token = rval + return new_headers + + def get_token(self): + return self.auth_token + +# This is needed for VncApi._authenticate invocation from within Api server. +# We don't have access to user information so we hard code admin credentials. +def ks_admin_authenticate(self, response=None, headers=None): + rval = token_from_user_info('admin', 'admin', 'default-domain', 'cloud-admin') + new_headers = {} + new_headers['X-AUTH-TOKEN'] = rval + return new_headers + +# aaa-mode is ignored if multi-tenancy is configured +class TestRbacMtDisabled(test_case.ApiServerTestCase): + domain_name = 'default-domain' + fqdn = [domain_name] + vn_name='alice-vn' + + @classmethod + def setUpClass(cls): + extra_config_knobs = [ + ('DEFAULTS', 'aaa_mode', 'rbac'), + ('DEFAULTS', 'cloud_admin_role', 'cloud-admin'), + ('DEFAULTS', 'multi_tenancy', False), + ] + super(TestRbacMtDisabled, cls).setUpClass(extra_config_knobs=extra_config_knobs) + + def setUp(self): + super(TestRbacMtDisabled, self).setUp() + self._vnc_lib = VncApi('u', 'p', api_server_host=self._api_server_ip, + api_server_port=self._api_server_port) + + def test_rbac_config(self): + rv_json = self._vnc_lib._request(rest.OP_GET, '/aaa-mode') + rv = json.loads(rv_json) + self.assertNotEquals(rv["aaa-mode"], "rbac") + self.assertEquals(rv["aaa-mode"], "no-auth") + + rv_json = self._vnc_lib._request(rest.OP_GET, '/multi-tenancy') + rv = json.loads(rv_json) + self.assertEquals(rv["enabled"], False) + + def tearDown(self): + super(TestRbacMtDisabled, self).tearDown() + # end tearDown + +# aaa-mode is ignored if multi-tenancy is configured +class TestRbacMtEnabled(test_case.ApiServerTestCase): + + @classmethod + def setUpClass(cls): + extra_config_knobs = [ + ('DEFAULTS', 'aaa_mode', 'rbac'), + ('DEFAULTS', 'cloud_admin_role', 'cloud-admin'), + ('DEFAULTS', 'multi_tenancy', True), + ] + super(TestRbacMtEnabled, cls).setUpClass(extra_config_knobs=extra_config_knobs) + + def setUp(self): + super(TestRbacMtEnabled, self).setUp() + self._vnc_lib = VncApi('u', 'p', api_server_host=self._api_server_ip, + api_server_port=self._api_server_port) + + def test_rbac_config(self): + rv_json = self._vnc_lib._request(rest.OP_GET, '/aaa-mode') + rv = json.loads(rv_json) + self.assertNotEquals(rv["aaa-mode"], "rbac") + self.assertEquals(rv["aaa-mode"], "cloud-admin") + + rv_json = self._vnc_lib._request(rest.OP_GET, '/multi-tenancy') + rv = json.loads(rv_json) + self.assertEquals(rv["enabled"], True) + + def tearDown(self): + super(TestRbacMtEnabled, self).tearDown() + # end tearDown + +# aaa-mode is not ignored if multi-tenancy is not configured +class TestRbacAaaModeRbac(test_case.ApiServerTestCase): + + @classmethod + def setUpClass(cls): + extra_config_knobs = [ + ('DEFAULTS', 'aaa_mode', 'rbac'), + ('DEFAULTS', 'cloud_admin_role', 'cloud-admin'), + ] + super(TestRbacAaaModeRbac, cls).setUpClass(extra_config_knobs=extra_config_knobs) + + def setUp(self): + super(TestRbacAaaModeRbac, self).setUp() + self._vnc_lib = VncApi('u', 'p', api_server_host=self._api_server_ip, + api_server_port=self._api_server_port) + + def test_rbac_config(self): + rv_json = self._vnc_lib._request(rest.OP_GET, '/aaa-mode') + rv = json.loads(rv_json) + self.assertEquals(rv["aaa-mode"], "rbac") + + rv_json = self._vnc_lib._request(rest.OP_GET, '/multi-tenancy') + rv = json.loads(rv_json) + self.assertEquals(rv["enabled"], True) + + def tearDown(self): + super(TestRbacAaaModeRbac, self).tearDown() + # end tearDown + +# aaa-mode is not ignored if multi-tenancy is not configured +class TestRbacAaaModeAdminOnly(test_case.ApiServerTestCase): + + @classmethod + def setUpClass(cls): + extra_config_knobs = [ + ('DEFAULTS', 'aaa_mode', 'cloud-admin'), + ('DEFAULTS', 'cloud_admin_role', 'cloud-admin'), + ] + super(TestRbacAaaModeAdminOnly, cls).setUpClass(extra_config_knobs=extra_config_knobs) + + def setUp(self): + super(TestRbacAaaModeAdminOnly, self).setUp() + self._vnc_lib = VncApi('u', 'p', api_server_host=self._api_server_ip, + api_server_port=self._api_server_port) + + def test_rbac_config(self): + rv_json = self._vnc_lib._request(rest.OP_GET, '/aaa-mode') + rv = json.loads(rv_json) + self.assertEquals(rv["aaa-mode"], "cloud-admin") + + rv_json = self._vnc_lib._request(rest.OP_GET, '/multi-tenancy') + rv = json.loads(rv_json) + self.assertEquals(rv["enabled"], True) + + def tearDown(self): + super(TestRbacAaaModeAdminOnly, self).tearDown() + # end tearDown + +# aaa-mode is not ignored if multi-tenancy is not configured +class TestRbacAaaModeNoAuth(test_case.ApiServerTestCase): + + @classmethod + def setUpClass(cls): + extra_config_knobs = [ + ('DEFAULTS', 'aaa_mode', 'no-auth'), + ('DEFAULTS', 'cloud_admin_role', 'cloud-admin'), + ] + super(TestRbacAaaModeNoAuth, cls).setUpClass(extra_config_knobs=extra_config_knobs) + + def setUp(self): + super(TestRbacAaaModeNoAuth, self).setUp() + self._vnc_lib = VncApi('u', 'p', api_server_host=self._api_server_ip, + api_server_port=self._api_server_port) + + def test_rbac_config(self): + rv_json = self._vnc_lib._request(rest.OP_GET, '/aaa-mode') + rv = json.loads(rv_json) + self.assertEquals(rv["aaa-mode"], "no-auth") + + rv_json = self._vnc_lib._request(rest.OP_GET, '/multi-tenancy') + rv = json.loads(rv_json) + self.assertEquals(rv["enabled"], False) + + def tearDown(self): + super(TestRbacAaaModeNoAuth, self).tearDown() + # end tearDown + +class TestRbacAaaModeInvalid(test_case.ApiServerTestCase): + + @classmethod + def setUpClass(cls): + extra_config_knobs = [ + ('DEFAULTS', 'aaa_mode', 'invalid-value'), + ('DEFAULTS', 'cloud_admin_role', 'cloud-admin'), + ] + super(TestRbacAaaModeInvalid, cls).setUpClass(extra_config_knobs=extra_config_knobs) + + def setUp(self): + super(TestRbacAaaModeInvalid, self).setUp() + self._vnc_lib = VncApi('u', 'p', api_server_host=self._api_server_ip, + api_server_port=self._api_server_port) + + def test_rbac_config(self): + rv_json = self._vnc_lib._request(rest.OP_GET, '/aaa-mode') + rv = json.loads(rv_json) + self.assertEquals(rv["aaa-mode"], "cloud-admin") + + rv_json = self._vnc_lib._request(rest.OP_GET, '/multi-tenancy') + rv = json.loads(rv_json) + self.assertEquals(rv["enabled"], True) + + def tearDown(self): + super(TestRbacAaaModeInvalid, self).tearDown() + # end tearDown + +class TestRbac(test_case.ApiServerTestCase): + + @classmethod + def setUpClass(cls): + extra_config_knobs = [ + ('DEFAULTS', 'aaa_mode', 'rbac'), + ] + super(TestRbac, cls).setUpClass(extra_config_knobs=extra_config_knobs) + + def setUp(self): + super(TestRbac, self).setUp() + self._vnc_lib = VncApi('u', 'p', api_server_host=self._api_server_ip, + api_server_port=self._api_server_port) + + def test_aaa_mode(self): + self.assertRaises(HttpError, self._vnc_lib.set_aaa_mode, "invalid-aaa-mode") + + def tearDown(self): + super(TestRbac, self).tearDown() + # end tearDown diff --git a/src/config/api-server/utils.py b/src/config/api-server/utils.py index a4af5267591..69ccd65bf9f 100644 --- a/src/config/api-server/utils.py +++ b/src/config/api-server/utils.py @@ -9,12 +9,14 @@ import ConfigParser import gen.resource_xsd import vnc_quota +import cfgm_common from pysandesh.sandesh_base import Sandesh, SandeshSystem from pysandesh.gen_py.sandesh.ttypes import SandeshLevel _WEB_HOST = '0.0.0.0' _WEB_PORT = 8082 _ADMIN_PORT = 8095 +_CLOUD_ADMIN_ROLE = 'admin' def parse_args(args_str): args_obj = None @@ -51,8 +53,8 @@ def parse_args(args_str): 'logging_level': 'WARN', 'logging_conf': '', 'logger_class': None, - 'multi_tenancy': True, - 'multi_tenancy_with_rbac': False, + 'multi_tenancy': None, + 'aaa_mode': cfgm_common.AAA_MODE_DEFAULT_VALUE, 'disc_server_ip': None, 'disc_server_port': '5998', 'zk_server_ip': '127.0.0.1:2181', @@ -69,6 +71,7 @@ def parse_args(args_str): 'sandesh_send_rate_limit': SandeshSystem.get_sandesh_send_rate_limit(), 'ifmap_health_check_interval': '60', # in seconds 'stale_lock_seconds': '5', # lock but no resource past this => stale + 'cloud_admin_role': _CLOUD_ADMIN_ROLE, 'rabbit_use_ssl': False, 'kombu_ssl_version': '', 'kombu_ssl_keyfile': '', @@ -109,8 +112,6 @@ def parse_args(args_str): if 'multi_tenancy' in config.options('DEFAULTS'): defaults['multi_tenancy'] = config.getboolean( 'DEFAULTS', 'multi_tenancy') - if 'multi_tenancy_with_rbac' in config.options('DEFAULTS'): - defaults['multi_tenancy_with_rbac'] = config.getboolean('DEFAULTS', 'multi_tenancy_with_rbac') if 'default_encoding' in config.options('DEFAULTS'): default_encoding = config.get('DEFAULTS', 'default_encoding') gen.resource_xsd.ExternalEncoding = default_encoding @@ -242,8 +243,8 @@ def parse_args(args_str): "--multi_tenancy", action="store_true", help="Validate resource permissions (implies token validation)") parser.add_argument( - "--multi_tenancy_with_rbac", action="store_true", - help="Validate API and resource permissions (implies token validation)") + "--aaa_mode", choices=cfgm_common.AAA_MODE_VALID_VALUES, + help="AAA mode") parser.add_argument( "--worker_id", help="Worker Id") @@ -287,6 +288,8 @@ def parse_args(args_str): help="Interval seconds to check for ifmap health, default 60") parser.add_argument("--stale_lock_seconds", help="Time after which lock without resource is stale, default 60") + parser.add_argument( "--cloud_admin_role", + help="Role name of cloud administrator") args_obj, remaining_argv = parser.parse_known_args(remaining_argv) args_obj.config_sections = config if type(args_obj.cassandra_server_list) is str: diff --git a/src/config/api-server/vnc_auth_keystone.py b/src/config/api-server/vnc_auth_keystone.py index e9eee8f71b8..6de6158d42d 100644 --- a/src/config/api-server/vnc_auth_keystone.py +++ b/src/config/api-server/vnc_auth_keystone.py @@ -14,7 +14,7 @@ import bottle import time import base64 - +import re try: from keystoneclient.middleware import auth_token except ImportError: @@ -92,9 +92,15 @@ def get_mt(self): def set_mt(self, value): self.mt = value + def path_in_white_list(self, path): + for pattern in self.conf['api_server'].white_list: + if re.search(pattern, path): + return True + return False + def __call__(self, env, start_response): - if (env.get('PATH_INFO') and - env['PATH_INFO'].startswith('/documentation')): + if self.path_in_white_list(env['PATH_INFO']): + env['HTTP_X_ROLE'] = '' app = bottle.app() else: app = self.app if self.mt else bottle.app() @@ -137,6 +143,7 @@ def __init__(self, server_mgr, args): and args.auth_protocol == 'https': certs=[args.certfile, args.keyfile, args.cafile] _kscertbundle=cfgmutils.getCertKeyCaBundle(_DEFAULT_KS_CERT_BUNDLE,certs) + identity_uri = '%s://%s:%s' % (args.auth_protocol, args.auth_host, args.auth_port) self._conf_info = { 'auth_host': args.auth_host, 'auth_port': args.auth_port, @@ -147,20 +154,21 @@ def __init__(self, server_mgr, args): 'admin_port': args.admin_port, 'max_requests': args.max_requests, 'insecure':args.insecure, + 'identity_uri': identity_uri, } try: if 'v3' in args.auth_url: self._conf_info['auth_version'] = 'v3.0' + self._conf_info['auth_uri'] = args.auth_url except AttributeError: pass if _kscertbundle: self._conf_info['cafile'] = _kscertbundle self._server_mgr = server_mgr self._auth_method = args.auth - self._auth_token = None self._auth_middleware = None - self._mt_rbac = args.multi_tenancy_with_rbac - self._multi_tenancy = args.multi_tenancy or args.multi_tenancy_with_rbac + self._mt_rbac = server_mgr.is_rbac_enabled() + self._multi_tenancy = server_mgr.is_multi_tenancy_set() if not self._auth_method: return if self._auth_method != 'keystone': @@ -179,35 +187,6 @@ def __init__(self, server_mgr, args): self._conf_info['token_cache_time'] = args.token_cache_time # end __init__ - def json_request(self, method, path, retry_after_authn=False): - if self._auth_token is None or self._auth_middleware is None: - return {} - headers = {'X-Auth-Token': self._auth_token} - response, data = self._auth_middleware._json_request( - method, path, additional_headers=headers) - try: - status_code = response.status_code - except AttributeError: - status_code = response.status - - # avoid multiple reauth - if ((status_code == 401) and (not retry_after_authn)): - try: - self._auth_token = self._auth_middleware.get_admin_token() - return self.json_request(method, path, retry_after_authn=True) - except Exception as e: - self._server_mgr.config_log( - "Error in getting admin token from keystone: " + str(e), - level=SandeshLevel.SYS_WARN) - return {} - - return data if status_code == 200 else {} - # end json_request - - def get_projects(self): - return self.json_request('GET', '/v2.0/tenants') - # end get_projects - def get_middleware_app(self): if not self._auth_method: return None @@ -222,16 +201,6 @@ def get_middleware_app(self): auth_middleware = auth_token.AuthProtocol(app, self._conf_info) self._auth_middleware = auth_middleware - while True: - try: - self._auth_token = auth_middleware.get_admin_token() - break - except auth_token.ServiceError as e: - msg = "Error in getting admin token: " + str(e) - time.sleep(2) - - self._server_mgr.config_log("Auth token fetched from keystone.", - level=SandeshLevel.SYS_NOTICE) # open access for troubleshooting admin_port = self._conf_info['admin_port'] @@ -243,7 +212,7 @@ def get_middleware_app(self): # allow multi tenancy to be updated dynamically app = AuthPreKeystone( auth_middleware, - {'admin_token': self._auth_token}, + { 'api_server': self._server_mgr }, self._multi_tenancy) return app @@ -281,21 +250,4 @@ def validate_user_token(self, request): auth_middleware = auth_token.AuthProtocol(self.token_valid, conf_info) return auth_middleware(request.headers.environ, self.start_response) - # convert keystone user id to name - def user_id_to_name(self, id): - if id in self._ks_users: - return self._ks_users[id] - - # fetch from keystone - content = self.json_request('GET', '/v2.0/users') - if 'users' in content: - self._ks_users = dict((user['id'], user['name']) - for user in content['users']) - - # check it again - if id in self._ks_users: - return self._ks_users[id] - else: - return '' - # end user_id_to_name # end class AuthService diff --git a/src/config/api-server/vnc_cfg_api_server.py b/src/config/api-server/vnc_cfg_api_server.py index e0a197bd5ab..1b4f008f619 100644 --- a/src/config/api-server/vnc_cfg_api_server.py +++ b/src/config/api-server/vnc_cfg_api_server.py @@ -62,6 +62,7 @@ import vnc_cfg_types from vnc_cfg_ifmap import VncDbClient +import cfgm_common from cfgm_common import ignore_exceptions, imid from cfgm_common.uve.vnc_api.ttypes import VncApiCommon, VncApiConfigLog,\ VncApiError @@ -140,8 +141,8 @@ 'method': 'POST', 'method_name': 'obj_chmod_http_post'}, {'uri': '/multi-tenancy', 'link_name': 'multi-tenancy', 'method': 'PUT', 'method_name': 'mt_http_put'}, - {'uri': '/multi-tenancy-with-rbac', 'link_name': 'rbac', - 'method': 'PUT', 'method_name': 'rbac_http_put'}, + {'uri': '/aaa-mode', 'link_name': 'aaa-mode', + 'method': 'PUT', 'method_name': 'aaa_mode_http_put'}, ] @@ -987,7 +988,7 @@ def internal_request_create(self, resource_type, obj_json): {'PATH_INFO': '/%ss' %(resource_type), 'bottle.app': orig_request.environ['bottle.app'], 'HTTP_X_USER': 'contrail-api', - 'HTTP_X_ROLE': 'admin'}) + 'HTTP_X_ROLE': self.cloud_admin_role}) json_as_dict = {'%s' %(resource_type): obj_json} i_req = context.ApiInternalRequest( b_req.url, b_req.urlparts, b_req.environ, b_req.headers, @@ -1007,7 +1008,7 @@ def internal_request_update(self, resource_type, obj_uuid, obj_json): {'PATH_INFO': '/%ss' %(resource_type), 'bottle.app': orig_request.environ['bottle.app'], 'HTTP_X_USER': 'contrail-api', - 'HTTP_X_ROLE': 'admin'}) + 'HTTP_X_ROLE': self.cloud_admin_role}) json_as_dict = {'%s' %(resource_type): obj_json} i_req = context.ApiInternalRequest( b_req.url, b_req.urlparts, b_req.environ, b_req.headers, @@ -1027,7 +1028,7 @@ def internal_request_delete(self, resource_type, obj_uuid): {'PATH_INFO': '/%s/%s' %(resource_type, obj_uuid), 'bottle.app': orig_request.environ['bottle.app'], 'HTTP_X_USER': 'contrail-api', - 'HTTP_X_ROLE': 'admin'}) + 'HTTP_X_ROLE': self.cloud_admin_role}) i_req = context.ApiInternalRequest( b_req.url, b_req.urlparts, b_req.environ, b_req.headers, None, None) @@ -1053,7 +1054,7 @@ def internal_request_ref_update(self, {'PATH_INFO': '/ref-update', 'bottle.app': orig_request.environ['bottle.app'], 'HTTP_X_USER': 'contrail-api', - 'HTTP_X_ROLE': 'admin'}) + 'HTTP_X_ROLE': self.cloud_admin_role}) i_req = context.ApiInternalRequest( b_req.url, b_req.urlparts, b_req.environ, b_req.headers, req_dict, None) @@ -1209,6 +1210,16 @@ def __init__(self, args_str=None): args_str = ' '.join(sys.argv[1:]) self._parse_args(args_str) + # aaa-mode is ignored if multi_tenancy is configured by user + if self._args.multi_tenancy is None: + # MT unconfigured by user - determine from aaa-mode + if self.aaa_mode not in cfgm_common.AAA_MODE_VALID_VALUES: + self.aaa_mode = cfgm_common.AAA_MODE_DEFAULT_VALUE + self._args.multi_tenancy = self.aaa_mode != 'no-auth' + else: + # MT configured by user - ignore aaa-mode + self.aaa_mode = "cloud-admin" if self._args.multi_tenancy else "no-auth" + # set python logging level from logging_level cmdline arg if not self._args.logging_conf: logging.basicConfig(level = getattr(logging, self._args.logging_level)) @@ -1377,8 +1388,8 @@ def __init__(self, args_str=None): # Enable/Disable multi tenancy self.route('/multi-tenancy', 'GET', self.mt_http_get) self.route('/multi-tenancy', 'PUT', self.mt_http_put) - self.route('/multi-tenancy-with-rbac', 'GET', self.rbac_http_get) - self.route('/multi-tenancy-with-rbac', 'PUT', self.rbac_http_put) + self.route('/aaa-mode', 'GET', self.aaa_mode_http_get) + self.route('/aaa-mode', 'PUT', self.aaa_mode_http_put) # Initialize discovery client self._disc = None @@ -1469,7 +1480,7 @@ def __init__(self, args_str=None): # after db init (uses db_conn) self._rbac = vnc_rbac.VncRbac(self, self._db_conn) self._permissions = vnc_perms.VncPermissions(self, self._args) - if self._args.multi_tenancy_with_rbac: + if self.is_rbac_enabled(): self._create_default_rbac_rule() # Cpuinfo interface @@ -1483,6 +1494,11 @@ def __init__(self, args_str=None): self.re_uuid = re.compile('^[0-9A-F]{8}-?[0-9A-F]{4}-?4[0-9A-F]{3}-?[89AB][0-9A-F]{3}-?[0-9A-F]{12}$', re.IGNORECASE) + # following allowed without authentication + self.white_list = [ + '^/documentation', # allow all documentation + '^/$', # allow discovery + ] # end __init__ def sandesh_disc_client_subinfo_handle_request(self, req): @@ -1686,7 +1702,7 @@ def is_admin_request(self): for field in ('HTTP_X_API_ROLE', 'HTTP_X_ROLE'): if field in env: roles = env[field].split(',') - return 'admin' in [x.lower() for x in roles] + return self.cloud_admin_role in [x.lower() for x in roles] return False # Check for the system created VN. Disallow such VN delete @@ -1743,14 +1759,8 @@ def obj_perms_http_get(self): if 'token' not in get_request().query: raise cfgm_common.exceptions.HttpError( 400, 'User token needed for validation') - if 'uuid' not in get_request().query: - raise cfgm_common.exceptions.HttpError( - 400, 'Object uuid needed for validation') - obj_uuid = get_request().query.uuid user_token = get_request().query.token.encode("ascii") - result = {'permissions' : ''} - # get permissions in internal context try: orig_context = get_context() @@ -1764,18 +1774,25 @@ def obj_perms_http_get(self): i_req = context.ApiInternalRequest( b_req.url, b_req.urlparts, b_req.environ, b_req.headers, None, None) set_context(context.ApiContext(internal_req=i_req)) - if self._auth_svc.validate_user_token(get_request()): - result['permissions']= self._permissions.obj_perms(get_request(), obj_uuid) + token_info = self._auth_svc.validate_user_token(get_request()) finally: set_context(orig_context) + # roles in result['token_info']['access']['user']['roles'] + if token_info: + result = {'token_info' : token_info} + if 'uuid' in get_request().query: + obj_uuid = get_request().query.uuid + result['permissions'] = self._permissions.obj_perms(get_request(), obj_uuid) + else: + raise cfgm_common.exceptions.HttpError(403, " Permission denied") return result #end check_obj_perms_http_get def invalid_uuid(self, uuid): return self.re_uuid.match(uuid) == None def invalid_access(self, access): - return type(access) is not int or access not in range(0,7) + return type(access) is not int or access not in range(0,8) # change ownership of an object def obj_chown_http_post(self): @@ -2292,15 +2309,22 @@ def fq_name_to_id_http_post(self): ok, result = self._permissions.check_perms_read(bottle.request, id) if not ok: err_code, err_msg = result - bottle.abort(err_code, err_msg) + raise cfgm_common.exceptions.HttpError(err_code, err_msg) return {'uuid': id} # end fq_name_to_id_http_post def id_to_fq_name_http_post(self): self._post_common(get_request(), None, None) + obj_uuid = get_request().json['uuid'] + + # ensure user has access to this id + ok, result = self._permissions.check_perms_read(get_request(), obj_uuid) + if not ok: + err_code, err_msg = result + raise cfgm_common.exceptions.HttpError(err_code, err_msg) + try: - obj_uuid = get_request().json['uuid'] fq_name = self._db_conn.uuid_to_fq_name(obj_uuid) except NoIdError: raise cfgm_common.exceptions.HttpError( @@ -2607,7 +2631,8 @@ def _get_default_id_perms(self, obj_type): return id_perms_dict # end _get_default_id_perms - def _ensure_perms2_present(self, obj_type, obj_uuid, obj_dict, project_id=None): + def _ensure_perms2_present(self, obj_type, obj_uuid, obj_dict, + project_id=None): """ Called at resource creation to ensure that id_perms is present in obj """ @@ -2625,14 +2650,13 @@ def _ensure_perms2_present(self, obj_type, obj_uuid, obj_dict, project_id=None): # Resource creation if obj_uuid is None: obj_dict['perms2'] = perms2 - return + return (True, "") # Resource already exist try: obj_dict['perms2'] = self._db_conn.uuid_to_obj_perms2(obj_uuid) except NoIdError: obj_dict['perms2'] = perms2 - - return + return (True, "") # retrieve the previous version of the perms2 # from the database and update the perms2 with @@ -2653,6 +2677,14 @@ def _ensure_perms2_present(self, obj_type, obj_uuid, obj_dict, project_id=None): # TODO handle perms2 present in req_perms2 obj_dict['perms2'] = perms2 + + # ensure is_shared and global_access are consistent + shared = obj_dict.get('is_shared', None) + gaccess = obj_dict['perms2'].get('global_access', None) + if gaccess is not None and shared is not None and shared != (gaccess != 0): + error = "Inconsistent is_shared (%s a) and global_access (%s)" % (shared, gaccess) + return (False, (400, error)) + return (True, "") # end _ensure_perms2_present def _get_default_perms2(self, obj_type): @@ -2703,35 +2735,55 @@ def _db_init_entries(self): # generate default rbac group rule def _create_default_rbac_rule(self): - obj_type = 'api-access-list' - fq_name = ['default-domain', 'default-api-access-list'] + obj_type = 'api_access_list' + fq_name = ['default-global-system-config', 'default-api-access-list'] try: id = self._db_conn.fq_name_to_uuid(obj_type, fq_name) - msg = 'RBAC: %s already exists ... leaving rules intact' % fq_name - self.config_log(msg, level=SandeshLevel.SYS_NOTICE) return except NoIdError: - self._create_singleton_entry(ApiAccessList(parent_type='domain', fq_name=fq_name)) - - id = self._db_conn.fq_name_to_uuid(obj_type, fq_name) - (ok, obj_dict) = self._db_conn.dbe_read(obj_type, {'uuid': id}) + pass # allow full access to cloud admin rbac_rules = [ { - 'rule_object':'*', + 'rule_object':'fqname-to-id', 'rule_field': '', - 'rule_perms': [{'role_name':'admin', 'role_crud':'CRUD'}] + 'rule_perms': [{'role_name':'*', 'role_crud':'CRUD'}] }, { - 'rule_object':'fqname-to-id', + 'rule_object':'id-to-fqname', 'rule_field': '', 'rule_perms': [{'role_name':'*', 'role_crud':'CRUD'}] }, + { + 'rule_object':'documentation', + 'rule_field': '', + 'rule_perms': [{'role_name':'*', 'role_crud':'R'}] + }, + { + 'rule_object':'/', + 'rule_field': '', + 'rule_perms': [{'role_name':'*', 'role_crud':'R'}] + }, ] - obj_dict['api_access_list_entries'] = {'rbac_rule' : rbac_rules} - self._db_conn.dbe_update(obj_type, {'uuid': id}, obj_dict) + rge = RbacRuleEntriesType([]) + for rule in rbac_rules: + rule_perms = [RbacPermType(role_name=p['role_name'], role_crud=p['role_crud']) for p in rule['rule_perms']] + rbac_rule = RbacRuleType(rule_object=rule['rule_object'], + rule_field=rule['rule_field'], rule_perms=rule_perms) + rge.add_rbac_rule(rbac_rule) + + rge_dict = rge.exportDict('') + glb_rbac_cfg = ApiAccessList(parent_type='global-system-config', + fq_name=fq_name, api_access_list_entries = rge_dict) + + try: + self._create_singleton_entry(glb_rbac_cfg) + except Exception as e: + err_msg = 'Error creating default api access list object' + err_msg += cfgm_common.utils.detailed_traceback() + self.config_log(err_msg, level=SandeshLevel.SYS_ERR) # end _create_default_rbac_rule def _resync_domains_projects(self, ext): @@ -2800,7 +2852,11 @@ def _list_collection(self, resource_type, parent_uuids=None, env = get_request().headers.environ tenant_uuid = env.get('HTTP_X_PROJECT_ID', None) shares = self._db_conn.get_shared_objects(obj_type, tenant_uuid) if tenant_uuid else [] + owned_objs = set([obj_uuid for (fq_name, obj_uuid) in result]) for (obj_uuid, obj_perm) in shares: + # skip owned objects already included in results + if obj_uuid in owned_objs: + continue try: fq_name = self._db_conn.uuid_to_fq_name(obj_uuid) result.append((fq_name, obj_uuid)) @@ -3263,18 +3319,14 @@ def set_mt(self, multi_tenancy): # end def is_multi_tenancy_set(self): - return self._args.multi_tenancy or self._args.multi_tenancy_with_rbac - - def is_multi_tenancy_with_rbac_set(self): - return self._args.multi_tenancy_with_rbac + return self._args.multi_tenancy or self.aaa_mode != 'no-auth' - def set_multi_tenancy_with_rbac(self, rbac_flag): - self._args.multi_tenancy_with_rbac = rbac_flag - # end + def is_rbac_enabled(self): + return self.aaa_mode == 'rbac' def mt_http_get(self): pipe_start_app = self.get_pipe_start_app() - mt = False + mt = self.is_multi_tenancy_set() try: mt = pipe_start_app.get_mt() except AttributeError: @@ -3296,19 +3348,32 @@ def mt_http_put(self): return {'enabled': self.is_multi_tenancy_set()} # end + @property + def aaa_mode(self): + return self._args.aaa_mode + + @aaa_mode.setter + def aaa_mode(self, mode): + self._args.aaa_mode = mode + # indication if multi tenancy with rbac is enabled or disabled - def rbac_http_get(self): - return {'enabled': self._args.multi_tenancy_with_rbac} + def aaa_mode_http_get(self): + return {'aaa-mode': self.aaa_mode} + + def aaa_mode_http_put(self): + aaa_mode = get_request().json['aaa-mode'] + if aaa_mode not in cfgm_common.AAA_MODE_VALID_VALUES: + raise ValueError('Invalid aaa-mode %s' % aaa_mode) - def rbac_http_put(self): - multi_tenancy_with_rbac = get_request().json['enabled'] if not self._auth_svc.validate_user_token(get_request()): raise cfgm_common.exceptions.HttpError(403, " Permission denied") if not self.is_admin_request(): raise cfgm_common.exceptions.HttpError(403, " Permission denied") - self.set_multi_tenancy_with_rbac(multi_tenancy_with_rbac) - return {'enabled': self.is_multi_tenancy_with_rbac_set()} + self.aaa_mode = aaa_mode + if self.is_rbac_enabled(): + self._create_default_rbac_rule() + return {'aaa-mode': self.aaa_mode} # end @property diff --git a/src/config/api-server/vnc_cfg_types.py b/src/config/api-server/vnc_cfg_types.py index 41ed1b08041..e75c752b076 100644 --- a/src/config/api-server/vnc_cfg_types.py +++ b/src/config/api-server/vnc_cfg_types.py @@ -28,6 +28,7 @@ from netaddr import IPNetwork from pprint import pformat from pysandesh.gen_py.sandesh.ttypes import SandeshLevel +from provision_defaults import * def _parse_rt(rt): @@ -906,6 +907,11 @@ def pre_dbe_create(cls, tenant_name, obj_dict, db_conn): if not ok: return (ok, response) user_visibility = obj_dict['id_perms'].get('user_visible', True) + # neutorn <-> vnc sharing + if obj_dict['perms2']['global_access']: + obj_dict['is_shared'] = True + elif obj_dict.get('is_shared'): + obj_dict['perms2']['global_access'] = PERMS_RWX verify_quota_kwargs = {'db_conn': db_conn, 'fq_name': obj_dict['fq_name'], 'resource': 'virtual_networks', @@ -976,6 +982,26 @@ def pre_dbe_update(cls, id, fq_name, obj_dict, db_conn, **kwargs): # Ignore ip-fabric subnet updates return True, "" + # neutron <-> vnc sharing + try: + global_access = obj_dict['perms2']['global_access'] + except KeyError: + global_access = None + is_shared = obj_dict.get('is_shared', None) + if global_access is not None or is_shared is not None: + if global_access is not None and is_shared is not None: + if is_shared != (global_access != 0): + error = "Inconsistent is_shared (%s a) and global_access (%s)" % (is_shared, global_access) + return (False, (400, error)) + elif global_access is not None: + obj_dict['is_shared'] = (global_access != 0) + else: + ok, result = cls.dbe_read(db_conn, 'virtual_network', id, obj_fields=['perms2']) + if not ok: + return ok, result + obj_dict['perms2'] = result['perms2'] + obj_dict['perms2']['global_access'] = PERMS_RWX if is_shared else 0 + (ok, error) = cls._check_route_targets(obj_dict, db_conn) if not ok: return (False, (400, error)) diff --git a/src/config/api-server/vnc_perms.py b/src/config/api-server/vnc_perms.py index 517ec0a1706..275786cba08 100644 --- a/src/config/api-server/vnc_perms.py +++ b/src/config/api-server/vnc_perms.py @@ -13,19 +13,26 @@ class VncPermissions(object): mode_str = {PERMS_R: 'R', PERMS_W: 'W', PERMS_X: 'X', PERMS_WX: 'WX', PERMS_RX: 'RX', PERMS_RW: 'RW', PERMS_RWX: 'RWX'} + mode_str2 = {PERMS_R: 'read', PERMS_W: 'write', PERMS_X: 'link', + PERMS_WX: 'write,link', PERMS_RX: 'read,link', PERMS_RW: 'read,write', + PERMS_RWX: 'read,write,link'} def __init__(self, server_mgr, args): self._server_mgr = server_mgr # end __init__ + @property + def cloud_admin_role(self): + return self._server_mgr.cloud_admin_role + @property def _multi_tenancy(self): - return self._server_mgr._args.multi_tenancy + return self._server_mgr.is_multi_tenancy_set() # end @property def _rbac(self): - return self._server_mgr._args.multi_tenancy_with_rbac + return self._server_mgr.is_rbac_enabled() # end def validate_user_visible_perm(self, id_perms, is_admin): @@ -43,7 +50,7 @@ def validate_perms(self, request, uuid, mode=PERMS_R, id_perms=None): err_msg = (403, 'Permission Denied') user, roles = self.get_user_roles(request) - is_admin = 'admin' in [x.lower() for x in roles] + is_admin = self.cloud_admin_role in [x.lower() for x in roles] if is_admin: return (True, 'RWX') @@ -77,18 +84,24 @@ def validate_perms_rbac(self, request, obj_uuid, mode=PERMS_R): # retrieve object and permissions try: - perms2 = self._server_mgr._db_conn.uuid_to_obj_perms2(obj_uuid) + config = self._server_mgr._db_conn.uuid_to_obj_dict(obj_uuid) + perms2 = json.loads(config.get('prop:perms2')) + obj_name = config.get("fq_name") + obj_type = config.get("type") except NoIdError: return (True, '') user, roles = self.get_user_roles(request) - is_admin = 'admin' in [x.lower() for x in roles] + is_admin = self.cloud_admin_role in [x.lower() for x in roles] if is_admin: return (True, 'RWX') env = request.headers.environ tenant = env.get('HTTP_X_PROJECT_ID', None) + tenant_name = env.get('HTTP_X_PROJECT_NAME', '*') if tenant is None: + msg = "rbac: Unable to find tenant id in headers" + self._server_mgr.config_log(msg, level=SandeshLevel.SYS_DEBUG) return (False, err_msg) owner = perms2['owner'] @@ -112,12 +125,15 @@ def validate_perms_rbac(self, request, obj_uuid, mode=PERMS_R): ok = (mask & perms & mode_mask) granted = ok & 07 | (ok >> 3) & 07 | (ok >> 6) & 07 - msg = '%s %s %s admin=%s, mode=%03o mask=%03o perms=%03o, \ - (usr=%s/own=%s/sh=%s)' \ - % ('+++' if ok else '---', self.mode_str[mode], obj_uuid, + msg = 'rbac: %s (%s:%s) %s %s admin=%s, mode=%03o mask=%03o perms=%03o, \ + (usr=%s(%s)/own=%s/sh=%s)' \ + % ('+++' if ok else '---', self.mode_str[mode], obj_uuid, obj_type, obj_name, 'yes' if is_admin else 'no', mode_mask, mask, perms, - tenant, owner, tenants) + tenant, tenant_name, owner, tenants) self._server_mgr.config_log(msg, level=SandeshLevel.SYS_DEBUG) + if not ok: + msg = "rbac: %s doesn't have %s permission in tenant %s" % (user, self.mode_str2[mode], owner) + self._server_mgr.config_log(msg, level=SandeshLevel.SYS_NOTICE) return (True, self.mode_str[granted]) if ok else (False, err_msg) # end validate_perms @@ -199,5 +215,3 @@ def obj_perms(self, request, id): return perms if ok else '' # end obj_perms - - diff --git a/src/config/api-server/vnc_rbac.py b/src/config/api-server/vnc_rbac.py index fad5adf770e..363df3654fc 100644 --- a/src/config/api-server/vnc_rbac.py +++ b/src/config/api-server/vnc_rbac.py @@ -14,14 +14,19 @@ class VncRbac(object): op_str = {'GET': 'R', 'POST': 'C', 'PUT': 'U', 'DELETE': 'D'} + op_str2 = {'GET': 'read', 'POST': 'create', 'PUT': 'update', 'DELETE': 'delete'} def __init__(self, server_mgr, db_conn): self._db_conn = db_conn self._server_mgr = server_mgr # end __init__ + @property + def cloud_admin_role(self): + return self._server_mgr.cloud_admin_role + def multi_tenancy_with_rbac(self): - return self._server_mgr.is_multi_tenancy_with_rbac_set() + return self._server_mgr.is_rbac_enabled() # end def validate_user_visible_perm(self, id_perms, is_admin): @@ -65,6 +70,26 @@ def read_default_rbac_rules(self, conf_file): rbac_rules.append(rule) return rbac_rules + def get_rbac_rules_object(self, obj_type, obj_uuid): + obj_ids = {'uuid' : obj_uuid} + obj_fields = ['api_access_lists'] + try: + (ok, result) = self._db_conn.dbe_read(obj_type, obj_ids, obj_fields) + except NoIdError: + ok = False + if not ok or 'api_access_lists' not in result: + return [] + api_access_lists = result['api_access_lists'] + + obj_fields = ['api_access_list_entries'] + obj_ids = {'uuid' : api_access_lists[0]['uuid']} + (ok, result) = self._db_conn.dbe_read('api_access_list', obj_ids, obj_fields) + if not ok or 'api_access_list_entries' not in result: + return rule_list + # {u'rbac_rule': [{u'rule_object': u'*', u'rule_perms': [{u'role_crud': u'CRUD', u'role_name': u'admin'}], u'rule_field': None}]} + api_access_list_entries = result['api_access_list_entries'] + return api_access_list_entries['rbac_rule'] + def get_rbac_rules(self, request): rule_list = [] env = request.headers.environ @@ -94,44 +119,21 @@ def get_rbac_rules(self, request): # print 'project:%s, domain:%s ' % (project_id, domain_id) - # get domain rbac group - obj_fields = ['api_access_lists'] - obj_ids = {'uuid' : domain_id} - (ok, result) = self._db_conn.dbe_read('domain', obj_ids, obj_fields) - if not ok or 'api_access_lists' not in result: - return rule_list - api_access_lists = result['api_access_lists'] + # get global rbac group + config_uuid = self._db_conn.fq_name_to_uuid('global_system_config', ['default-global-system-config']) + rules = self.get_rbac_rules_object('global_system_config', config_uuid) + rule_list.extend(rules) - obj_fields = ['api_access_list_entries'] - obj_ids = {'uuid' : api_access_lists[0]['uuid']} - (ok, result) = self._db_conn.dbe_read('api-access-list', obj_ids, obj_fields) - if not ok or 'api_access_list_entries' not in result: - return rule_list - # {u'rbac_rule': [{u'rule_object': u'*', u'rule_perms': [{u'role_crud': u'CRUD', u'role_name': u'admin'}], u'rule_field': None}]} - api_access_list_entries = result['api_access_list_entries'] - rule_list.extend(api_access_list_entries['rbac_rule']) + # get domain rbac group + rules = self.get_rbac_rules_object('domain', domain_id) + rule_list.extend(rules) # get project rbac group if project_id is None: return rule_list - obj_fields = ['api_access_lists'] - obj_ids = {'uuid' : project_id} - try: - (ok, result) = self._db_conn.dbe_read('project', obj_ids, obj_fields) - except Exception as e: - ok = False - if not ok or 'api_access_lists' not in result: - return rule_list - api_access_lists = result['api_access_lists'] - - obj_fields = ['api_access_list_entries'] - obj_ids = {'uuid' : api_access_lists[0]['uuid']} - (ok, result) = self._db_conn.dbe_read('api-access-list', obj_ids, obj_fields) - if not ok or 'api_access_list_entries' not in result: - return rule_list - api_access_list_entries = result['api_access_list_entries'] - rule_list.extend(api_access_list_entries['rbac_rule']) + rules = self.get_rbac_rules_object('project', project_id) + rule_list.extend(rules) # [{u'rule_object': u'*', u'rule_perms': [{u'role_crud': u'CRUD', u'role_name': u'admin'}], u'rule_field': None}] @@ -179,6 +181,7 @@ def request_path_to_obj_type(self, path): def validate_request(self, request): domain_id = request.headers.environ.get('HTTP_X_DOMAIN_ID', None) project_id = request.headers.environ.get('HTTP_X_PROJECT_ID', None) + project_name = request.headers.environ.get('HTTP_X_PROJECT_NAME', '*') app = request.environ['bottle.app'] if app.config.local_auth or self._server_mgr.is_auth_disabled(): @@ -189,12 +192,15 @@ def validate_request(self, request): err_msg = (403, 'Permission Denied') user, roles = self.get_user_roles(request) - is_admin = 'admin' in [x.lower() for x in roles] + is_admin = self.cloud_admin_role in [x.lower() for x in roles] + # other checks redundant if admin + if is_admin: + return (True, '') # rule list for project/domain of the request rule_list = self.get_rbac_rules(request) if len(rule_list) == 0: - msg = 'Error: RBAC rule list empty!!' + msg = 'rbac: rule list empty!!' self._server_mgr.config_log(msg, level=SandeshLevel.SYS_NOTICE) return (False, err_msg) @@ -209,8 +215,8 @@ def validate_request(self, request): except Exception: obj_dict = {} - msg = 'u=%s, r=%s, o=%s, op=%s, rules=%d, proj:%s, dom:%s' \ - % (user, roles, obj_type, api_op, len(rule_list), project_id, domain_id) + msg = 'rbac: u=%s, r=%s, o=%s, op=%s, rules=%d, proj:%s(%s), dom:%s' \ + % (user, roles, obj_type, api_op, len(rule_list), project_id, project_name, domain_id) self._server_mgr.config_log(msg, level=SandeshLevel.SYS_DEBUG) # match all rules - longest prefix match wins @@ -225,6 +231,7 @@ def validate_request(self, request): ps += perm['role_name'] + ':' + perm['role_crud'] + ',' o_f = "%s.%s" % (o,f) if f else o # check CRUD perms if object and field matches + length = -1; match = False if o == '*' or \ (o == obj_type and (f is None or f == '*' or f == '')) or \ (o == obj_type and (f is not None and f in obj_dict)): @@ -234,6 +241,9 @@ def validate_request(self, request): length = 2 else: length = 1 + # skip rule with no matching op + if True not in [api_op in rc['role_crud'] for rc in p]: + continue role_match = [rc['role_name'] in (roles + ['*']) and api_op in rc['role_crud'] for rc in p] match = True if True in role_match else False result[length] = (idx, match) @@ -241,18 +251,20 @@ def validate_request(self, request): self._server_mgr.config_log(msg, level=SandeshLevel.SYS_DEBUG) idx += 1 - x = sorted(result.items(), reverse = True) - ok = x[0][1][1] + ok = False + if len(result) > 0: + x = sorted(result.items(), reverse = True) + ok = x[0][1][1] - # temporarily allow all access to admin till we figure out default creation of rbac group in domain - ok = ok or is_admin - - msg = "%s admin=%s, u=%s, r='%s'" \ + msg = "rbac: %s admin=%s, u=%s, r='%s'" \ % ('+++' if ok else '\n---', 'yes' if is_admin else 'no', user, string.join(roles, ',') ) self._server_mgr.config_log(msg, level=SandeshLevel.SYS_DEBUG) + if not ok: + msg = "rbac: %s doesn't have %s permission for %s" % (user, self.op_str2[request.method], obj_type) + self._server_mgr.config_log(msg, level=SandeshLevel.SYS_NOTICE) return (True, '') if ok else (False, err_msg) # end validate_request diff --git a/src/config/common/SConscript b/src/config/common/SConscript index 97f4b519195..b961928724a 100644 --- a/src/config/common/SConscript +++ b/src/config/common/SConscript @@ -24,6 +24,7 @@ local_sources = [ 'zkclient.py', 'exceptions.py', 'utils.py', + 'rbaclib.py', 'jsonutils.py', 'imid.py', 'svc_info.py', diff --git a/src/config/common/__init__.py b/src/config/common/__init__.py index feaf21504ee..e1e11f519e4 100644 --- a/src/config/common/__init__.py +++ b/src/config/common/__init__.py @@ -15,6 +15,9 @@ BGP_RTGT_MIN_ID = 8000000 SGID_MIN_ALLOC = 8000000 +AAA_MODE_DEFAULT_VALUE = 'cloud-admin' +AAA_MODE_VALID_VALUES = ['no-auth', 'cloud-admin', 'rbac'] + def obj_to_json(obj): return dict((k, v) for k, v in obj.__dict__.iteritems()) #end obj_to_json diff --git a/src/config/common/rbaclib.py b/src/config/common/rbaclib.py new file mode 100644 index 00000000000..5a3bdfff492 --- /dev/null +++ b/src/config/common/rbaclib.py @@ -0,0 +1,87 @@ +# +# Copyright (c) 2013 Juniper Networks, Inc. All rights reserved. +# +# Util to manage RBAC group and rules (add, delete etc)", +# +import argparse +import uuid as __uuid +import os +import re + +from vnc_api.vnc_api import * +from vnc_api.gen.resource_xsd import * +from cfgm_common.exceptions import * + +# match two rules (type RbacRuleType) +# r1 is operational, r2 is part of rbac group +# return (obj_type & Field match, rule is subset of existing rule, match index, merged rule +def match_rule(r1, r2): + if r1.rule_object != r2.rule_object: + return None + if r1.rule_field != r2.rule_field: + return None + + s1 = set(r.role_name+":"+r.role_crud for r in r1.rule_perms) + s2 = set(r.role_name+":"+r.role_crud for r in r2.rule_perms) + + d1 = {r.role_name:set(list(r.role_crud)) for r in r1.rule_perms} + d2 = {r.role_name:set(list(r.role_crud)) for r in r2.rule_perms} + + diffs = {} + for role, cruds in d2.items(): + diffs[role] = cruds - d1.get(role, set([])) + diffs = {role:crud for role,crud in diffs.items() if len(crud) != 0} + + merge = d2.copy() + for role, cruds in d1.items(): + merge[role] = cruds|d2.get(role, set([])) + + return [True, s1==s2, diffs, merge] +# end + +# check if rule already exists in rule list and returns its index if it does +def find_rule(rge, rule): + idx = 1 + for r in rge.rbac_rule: + m = match_rule(rule, r) + if m: + m[0] = idx + return m + idx += 1 + return None +# end + +def build_perms(rule, perm_dict): + rule.rule_perms = [] + for role_name, role_crud in perm_dict.items(): + rule.rule_perms.append(RbacPermType(role_name, "".join(role_crud))) +# end + +# build rule object from string form +# "useragent-kv *:CRUD" (Allow all operation on /useragent-kv API) +def build_rule(rule_str): + r = rule_str.split(" ", 1) if rule_str else [] + if len(r) < 2: + return None + + # [0] is object.field, [1] is list of perms + obj_field = r[0].split(".") + perms = r[1].split(",") + + o = obj_field[0] + f = obj_field[1] if len(obj_field) > 1 else None + o_f = "%s.%s" % (o,f) if f else o + + # perms eg ['foo:CRU', 'bar:CR'] + rule_perms = [] + for perm in perms: + p = perm.strip().split(":") + rule_perms.append(RbacPermType(role_name = p[0], role_crud = p[1])) + + # build rule + rule = RbacRuleType( + rule_object = o, + rule_field = f, + rule_perms = rule_perms) + return rule +#end diff --git a/src/config/common/tests/test_utils.py b/src/config/common/tests/test_utils.py index 95e73e1086a..3fb6c142eff 100644 --- a/src/config/common/tests/test_utils.py +++ b/src/config/common/tests/test_utils.py @@ -1012,7 +1012,7 @@ def get_admin_token(self): 'X-User-Name': self.conf['admin_user'], 'X-Project-Name': self.conf['admin_tenant_name'], 'X-Domain-Name' : 'default-domain', - 'X-Role': 'admin', + 'X-Role': 'cloud-admin', } rval = json.dumps(token_dict) # print '%%%% generated admin token %s %%%%' % rval diff --git a/src/config/schema-transformer/test/test_case.py b/src/config/schema-transformer/test/test_case.py index a10eaebbee2..7b49450ae51 100644 --- a/src/config/schema-transformer/test/test_case.py +++ b/src/config/schema-transformer/test/test_case.py @@ -54,6 +54,14 @@ def wait_to_delete_object(self, obj_class, obj_name): class STTestCase(test_common.TestCase): + @classmethod + def setUpClass(cls): + extra_config = [ + ('DEFAULTS', 'multi_tenancy', 'False'), + ('DEFAULTS', 'aaa_mode', 'no-auth'), + ] + super(STTestCase, cls).setUpClass(extra_config_knobs=extra_config) + def _class_str(self): return str(self.__class__).strip('').strip("'") diff --git a/src/config/utils/rbacutil.py b/src/config/utils/rbacutil.py index fadeaa16e72..6dd2804b5f1 100644 --- a/src/config/utils/rbacutil.py +++ b/src/config/utils/rbacutil.py @@ -11,6 +11,8 @@ from vnc_api.vnc_api import * from vnc_api.gen.resource_xsd import * from cfgm_common.exceptions import * +from cfgm_common.rbaclib import * +import cfgm_common example_usage = \ """ @@ -59,81 +61,6 @@ def show_rbac_rules(api_access_list_entries): print '' # end -# match two rules (type RbacRuleType) -# r1 is operational, r2 is part of rbac group -# return (obj_type & Field match, rule is subset of existing rule, match index, merged rule -def match_rule(r1, r2): - if r1.rule_object != r2.rule_object: - return None - if r1.rule_field != r2.rule_field: - return None - - s1 = set(r.role_name+":"+r.role_crud for r in r1.rule_perms) - s2 = set(r.role_name+":"+r.role_crud for r in r2.rule_perms) - - d1 = {r.role_name:set(list(r.role_crud)) for r in r1.rule_perms} - d2 = {r.role_name:set(list(r.role_crud)) for r in r2.rule_perms} - - diffs = {} - for role, cruds in d2.items(): - diffs[role] = cruds - d1.get(role, set([])) - diffs = {role:crud for role,crud in diffs.items() if len(crud) != 0} - - merge = d2.copy() - for role, cruds in d1.items(): - merge[role] = cruds|d2.get(role, set([])) - - return [True, s1==s2, diffs, merge] -# end - -# check if rule already exists in rule list and returns its index if it does -def find_rule(rge, rule): - idx = 1 - for r in rge.rbac_rule: - m = match_rule(rule, r) - if m: - m[0] = idx - return m - idx += 1 - return None -# end - -def build_perms(rule, perm_dict): - rule.rule_perms = [] - for role_name, role_crud in perm_dict.items(): - rule.rule_perms.append(RbacPermType(role_name, "".join(role_crud))) -# end - -# build rule object from string form -# "useragent-kv *:CRUD" (Allow all operation on /useragent-kv API) -def build_rule(rule_str): - r = rule_str.split(" ", 1) if rule_str else [] - if len(r) < 2: - return None - - # [0] is object.field, [1] is list of perms - obj_field = r[0].split(".") - perms = r[1].split(",") - - o = obj_field[0] - f = obj_field[1] if len(obj_field) > 1 else None - o_f = "%s.%s" % (o,f) if f else o - print 'rule: %s %s' % (o_f, r[1]) - - # perms eg ['foo:CRU', 'bar:CR'] - rule_perms = [] - for perm in perms: - p = perm.strip().split(":") - rule_perms.append(RbacPermType(role_name = p[0], role_crud = p[1])) - - # build rule - rule = RbacRuleType( - rule_object = o, - rule_field = f, - rule_perms = rule_perms) - return rule -#end - # Read VNC object. Return None if object doesn't exists def vnc_read_obj(vnc, obj_type, fq_name): method_name = obj_type.replace('-', '_') @@ -152,7 +79,8 @@ def parse_args(self): # domain:default-project:default-virtual-network defaults = { - 'name': 'default-domain:default-api-access-list' + 'aaa_mode': None, + 'name': 'default-global-system-config:default-api-access-list' } parser = argparse.ArgumentParser( @@ -167,15 +95,13 @@ def parse_args(self): '--op', choices = valid_ops, help="Operation to perform") parser.add_argument( '--name', help="colon seperated fully qualified name", - default='default-domain:default-api-access-list') + default='default-global-system-config:default-api-access-list') parser.add_argument('--uuid', help="object UUID") parser.add_argument('--user', help="User Name") parser.add_argument('--role', help="Role Name") parser.add_argument('--rule', help="Rule to add or delete") parser.add_argument( - '--on', help="Enable RBAC", action="store_true") - parser.add_argument( - '--off', help="Disable RBAC", action="store_true") + '--aaa_mode', choices = cfgm_common.AAA_MODE_VALID_VALUES, help="AAA mode") parser.add_argument( '--os-username', help="Keystone User Name", default=None) parser.add_argument( @@ -233,10 +159,6 @@ def get_ks_var(self, name): tenant_name = conf['tenant_name'] obj_type = 'api-access-list' -if vnc_op.args.on and vnc_op.args.off: - print 'Only one of --on or --off must be specified' - sys.exit(1) - ui = {} if vnc_op.args.user: ui['user'] = vnc_op.args.user @@ -248,22 +170,24 @@ def get_ks_var(self, name): vnc = VncApi(username, password, tenant_name, server[0], server[1], user_info=ui) -url = '/multi-tenancy-with-rbac' -if vnc_op.args.on or vnc_op.args.off: - data = {'enabled': vnc_op.args.on} +url = '/aaa-mode' +if vnc_op.args.aaa_mode: try: - rv = vnc._request_server(rest.OP_PUT, url, json.dumps(data)) + rv = vnc.set_aaa_mode(vnc_op.args.aaa_mode) except PermissionDenied: print 'Permission denied' sys.exit(1) +elif vnc_op.args.uuid and vnc_op.args.name: + print 'Only one of uuid and fqname should be specified' + sys.exit(1) try: rv_json = vnc._request_server(rest.OP_GET, url) rv = json.loads(rv_json) - print 'Rbac is %s' % ('enabled' if rv['enabled'] else 'disabled') + print 'AAA mode is %s' % rv['aaa-mode'] except Exception as e: print str(e) - print '*** Rbac not supported' + print 'Rbac not supported' sys.exit(1) if not vnc_op.args.uuid and not vnc_op.args.name: @@ -296,7 +220,11 @@ def get_ks_var(self, name): name = fq_name[-1] if len(fq_name) == 2: - pobj = vnc.domain_read(fq_name = fq_name[0:1]) + # could be in domain or global config + if fq_name[0] == 'default-global-system-config': + pobj = vnc.global_system_config_read(fq_name = fq_name[0:1]) + else: + pobj = vnc.domain_read(fq_name = fq_name[0:1]) else: pobj = vnc.project_read(fq_name = fq_name[0:2]) diff --git a/src/schema/vnc_cfg.xsd b/src/schema/vnc_cfg.xsd index 5f7ae76b781..00c7d0c222d 100644 --- a/src/schema/vnc_cfg.xsd +++ b/src/schema/vnc_cfg.xsd @@ -1964,6 +1964,10 @@ targetNamespace="http://www.contrailsystems.com/2012/VNC-CONFIG/0"> + +