Merge "Fixes converter not working for certain edge cases."
diff --git a/patrole_tempest_plugin/rbac_auth.py b/patrole_tempest_plugin/rbac_auth.py
index 88b7032..78cc951 100644
--- a/patrole_tempest_plugin/rbac_auth.py
+++ b/patrole_tempest_plugin/rbac_auth.py
@@ -14,7 +14,6 @@
# under the License.
from oslo_log import log as logging
-from tempest.lib import exceptions
from patrole_tempest_plugin import rbac_role_converter
@@ -22,22 +21,21 @@
class RbacAuthority(object):
- def __init__(self, component=None, service=None):
- self.converter = rbac_role_converter.RbacPolicyConverter(service)
- self.roles_dict = self.converter.rules
+ def __init__(self, tenant_id, service=None):
+ self.converter = rbac_role_converter.RbacPolicyConverter(tenant_id,
+ service)
- def get_permission(self, api, role):
- if self.roles_dict is None:
- raise exceptions.InvalidConfiguration("Roles dictionary is empty!")
+ def get_permission(self, rule_name, role):
try:
- _api = self.roles_dict[api]
- if role in _api:
- LOG.debug("[API]: %s, [Role]: %s is allowed!", api, role)
- return True
+ is_allowed = self.converter.allowed(rule_name, role)
+ if is_allowed:
+ LOG.debug("[API]: %s, [Role]: %s is allowed!", rule_name, role)
else:
- LOG.debug("[API]: %s, [Role]: %s is NOT allowed!", api, role)
- return False
+ LOG.debug("[API]: %s, [Role]: %s is NOT allowed!",
+ rule_name, role)
+ return is_allowed
except KeyError:
- raise KeyError("'%s' API is not defined in the policy.json"
- % api)
+ LOG.debug("[API]: %s, [Role]: %s is NOT allowed!",
+ rule_name, role)
+ return False
return False
diff --git a/patrole_tempest_plugin/rbac_role_converter.py b/patrole_tempest_plugin/rbac_role_converter.py
index cfb7856..65e3f27 100644
--- a/patrole_tempest_plugin/rbac_role_converter.py
+++ b/patrole_tempest_plugin/rbac_role_converter.py
@@ -13,141 +13,125 @@
# License for the specific language governing permissions and limitations
# under the License.
+import copy
import os
-from oslo_config import cfg
from oslo_log import log as logging
-from oslo_policy import _checks
from oslo_policy import policy
from tempest import config
-from patrole_tempest_plugin.rbac_exceptions import RbacResourceSetupFailed
+from patrole_tempest_plugin import rbac_exceptions
CONF = config.CONF
LOG = logging.getLogger(__name__)
-RULES_TO_SKIP = []
-TESTED_RULES = []
-PARSED_RULES = []
-
class RbacPolicyConverter(object):
"""A class for parsing policy rules into lists of allowed roles.
RBAC testing requires that each rule in a policy file be broken up into
the roles that constitute it. This class automates that process.
+
+ The list of roles per rule can be reverse-engineered by checking, for
+ each role, whether a given rule is allowed using oslo policy.
"""
- def __init__(self, service, path=None):
- """Initialization of Policy Converter
+ def __init__(self, tenant_id, service, path=None):
+ """Initialization of Policy Converter.
- Parse policy files to create dictionary mapping
- policy actions to roles.
+ Parse policy files to create dictionary mapping policy actions to
+ roles.
+
+ :param tenant_id: type uuid
:param service: type string
:param path: type string
"""
-
if path is None:
- path = '/etc/{0}/policy.json'.format(service)
+ self.path = '/etc/{0}/policy.json'.format(service)
+ else:
+ self.path = path
- if not os.path.isfile(path):
- raise RbacResourceSetupFailed('Policy file for service: {0}, {1}'
- ' not found.'.format(service, path))
+ if not os.path.isfile(self.path):
+ raise rbac_exceptions.RbacResourceSetupFailed(
+ 'Policy file for service: {0}, {1} not found.'
+ .format(service, self.path))
- self.default_roles = CONF.rbac.rbac_roles
- self.rules = {}
+ self.tenant_id = tenant_id
- self._get_roles_for_each_rule_in_policy_file(path)
+ def allowed(self, rule_name, role):
+ policy_file = open(self.path, 'r')
+ access_token = self._get_access_token(role)
- def _get_roles_for_each_rule_in_policy_file(self, path):
- """Gets the roles for each rule in the policy file at given path."""
+ is_allowed = self._allowed(
+ policy_file=policy_file,
+ access=access_token,
+ apply_rule=rule_name,
+ is_admin=False)
- global PARSED_RULES
- global TESTED_RULES
- global RULES_TO_SKIP
+ policy_file = open(self.path, 'r')
+ access_token = self._get_access_token(role)
+ allowed_as_admin_context = self._allowed(
+ policy_file=policy_file,
+ access=access_token,
+ apply_rule=rule_name,
+ is_admin=True)
- rule_to_roles_dict = {}
- enforcer = self._init_policy_enforcer(path)
+ if allowed_as_admin_context and is_allowed:
+ return True
+ if allowed_as_admin_context and not is_allowed:
+ return False
+ if not allowed_as_admin_context and is_allowed:
+ return True
+ if not allowed_as_admin_context and not is_allowed:
+ return False
- base_rules = set()
- for rule_name, rule_checker in enforcer.rules.items():
- if isinstance(rule_checker, _checks.OrCheck):
- for sub_rule in rule_checker.rules:
- if hasattr(sub_rule, 'match'):
- base_rules.add(sub_rule.match)
- elif isinstance(rule_checker, _checks.RuleCheck):
- if hasattr(rule_checker, 'match'):
- base_rules.add(rule_checker.match)
+ def _get_access_token(self, role):
+ access_token = {
+ "token": {
+ "roles": [
+ {
+ "name": role
+ }
+ ],
+ "project": {
+ "id": self.tenant_id
+ }
+ }
+ }
+ return access_token
- RULES_TO_SKIP.extend(base_rules)
- generic_check_dict = self._get_generic_check_dict(enforcer.rules)
+ def _allowed(self, policy_file, access, apply_rule, is_admin=False):
+ """Checks if a given rule in a policy is allowed with given access.
- for rule_name, rule_checker in enforcer.rules.items():
- PARSED_RULES.append(rule_name)
+ Adapted from oslo_policy.shell.
- if rule_name in RULES_TO_SKIP:
- continue
- if isinstance(rule_checker, _checks.GenericCheck):
- continue
-
- # Determine whether each role is contained within the current rule.
- for role in self.default_roles:
- roles = {'roles': [role]}
- roles.update(generic_check_dict)
- is_role_in_rule = rule_checker(
- generic_check_dict, roles, enforcer)
- if is_role_in_rule:
- rule_to_roles_dict.setdefault(rule_name, set())
- rule_to_roles_dict[rule_name].add(role)
-
- self.rules = rule_to_roles_dict
-
- def _init_policy_enforcer(self, policy_file):
- """Initializes oslo policy enforcer"""
-
- def find_file(path):
- realpath = os.path.realpath(path)
- if os.path.isfile(realpath):
- return realpath
- else:
- return None
-
- CONF = cfg.CONF
- CONF.find_file = find_file
-
- enforcer = policy.Enforcer(CONF,
- policy_file=policy_file,
- rules=None,
- default_rule=None,
- use_conf=True)
- enforcer.load_rules()
- return enforcer
-
- def _get_generic_check_dict(self, enforcer_rules):
- """Creates permissions dictionary that oslo policy uses
-
- to determine if a user can perform an action.
+ :param policy file: type string: path to policy file
+ :param access: type dict: dictionary from ``_get_access_token``
+ :param apply_rule: type string: rule to be checked
+ :param is_admin: type bool: whether admin context is used
"""
+ access_data = copy.copy(access['token'])
+ access_data['roles'] = [role['name'] for role in access_data['roles']]
+ access_data['project_id'] = access_data['project']['id']
+ access_data['is_admin'] = is_admin
+ policy_data = policy_file.read()
+ rules = policy.Rules.load(policy_data, "default")
- generic_checks = set()
- for rule_checker in enforcer_rules.values():
- entries = set()
- self._get_generic_check_entries(rule_checker, entries)
- generic_checks |= entries
- return {e: '' for e in generic_checks}
+ class Object(object):
+ pass
+ o = Object()
+ o.rules = rules
- def _get_generic_check_entries(self, rule_checker, entries):
- if isinstance(rule_checker, _checks.GenericCheck):
- if hasattr(rule_checker, 'match'):
- if rule_checker.match.startswith('%(') and\
- rule_checker.match.endswith(')s'):
- entries.add(rule_checker.match[2:-2])
- if hasattr(rule_checker, 'rule'):
- if isinstance(rule_checker.rule, _checks.GenericCheck) and\
- hasattr(rule_checker.rule, 'match'):
- if rule_checker.rule.match.startswith('%(') and\
- rule_checker.rule.match.endswith(')s'):
- entries.add(rule_checker.rule.match[2:-2])
- if hasattr(rule_checker, 'rules'):
- for rule in rule_checker.rules:
- self._get_generic_check_entries(rule, entries)
+ target = {"project_id": access_data['project_id']}
+
+ key = apply_rule
+ rule = rules[apply_rule]
+ result = self._try_rule(key, rule, target, access_data, o)
+ return result
+
+ def _try_rule(self, key, rule, target, access_data, o):
+ try:
+ return rule(target, access_data, o)
+ except Exception as e:
+ LOG.debug("Exception: {0} for rule: {1}".format(e, rule))
+ return False
diff --git a/patrole_tempest_plugin/rbac_rule_validation.py b/patrole_tempest_plugin/rbac_rule_validation.py
index e11ae4c..7f9d4d2 100644
--- a/patrole_tempest_plugin/rbac_rule_validation.py
+++ b/patrole_tempest_plugin/rbac_rule_validation.py
@@ -13,7 +13,8 @@
# License for the specific language governing permissions and limitations
# under the License.
-from oslo_log import log as logging
+import logging
+
from tempest import config
from tempest.lib import exceptions
@@ -24,10 +25,11 @@
LOG = logging.getLogger(__name__)
-def action(component, service, rule):
+def action(service, rule):
def decorator(func):
def wrapper(*args, **kwargs):
- authority = rbac_auth.RbacAuthority(component, service)
+ authority = rbac_auth.RbacAuthority(
+ args[0].auth_provider.credentials.tenant_id, service)
allowed = authority.get_permission(rule, CONF.rbac.rbac_test_role)
try:
diff --git a/patrole_tempest_plugin/tests/api/image/v2/test_images_member_rbac.py b/patrole_tempest_plugin/tests/api/image/v2/test_images_member_rbac.py
index c2d2969..bcfeea7 100644
--- a/patrole_tempest_plugin/tests/api/image/v2/test_images_member_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/v2/test_images_member_rbac.py
@@ -52,7 +52,7 @@
rbac_utils.switch_role(self, switchToRbacRole=False)
super(ImagesMemberRbacTest, self).setUp()
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="add_member")
@decorators.idempotent_id('b1b85ace-6484-11e6-881e-080027d0d606')
def test_add_image_member(self):
@@ -67,7 +67,7 @@
self.image_member_client.create_image_member(image_id,
member=self.alt_tenant_id)
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="delete_member")
@decorators.idempotent_id('ba075234-6484-11e6-881e-080027d0d606')
def test_delete_image_member(self):
@@ -84,7 +84,7 @@
self.image_member_client.delete_image_member(image_id,
self.alt_tenant_id)
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="get_member")
@decorators.idempotent_id('c01fd308-6484-11e6-881e-080027d0d606')
def test_show_image_member(self):
@@ -112,7 +112,7 @@
"image members")
raise rbac_exceptions.RbacActionFailed(e)
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="modify_member")
@decorators.idempotent_id('ca448bb2-6484-11e6-881e-080027d0d606')
def test_update_image_member(self):
@@ -131,7 +131,7 @@
image_id, self.image_client.tenant_id,
status='accepted')
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="get_members")
@decorators.idempotent_id('d0a2dc20-6484-11e6-881e-080027d0d606')
def test_list_image_members(self):
diff --git a/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py b/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
index c9f17bf..39b4cd9 100644
--- a/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
+++ b/patrole_tempest_plugin/tests/api/image/v2/test_images_rbac.py
@@ -39,7 +39,7 @@
rbac_utils.switch_role(self, switchToRbacRole=False)
super(BasicOperationsImagesRbacTest, self).tearDown()
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="add_image")
@decorators.idempotent_id('0f148510-63bf-11e6-b348-080027d0d606')
def test_create_image(self):
@@ -57,7 +57,7 @@
visibility='private',
ramdisk_id=uuid)
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="upload_image")
@decorators.idempotent_id('fdc0c7e2-ad58-4c5a-ba9d-1f6046a5b656')
def test_upload_image(self):
@@ -79,7 +79,7 @@
image_file = moves.cStringIO(data_utils.random_bytes())
self.client.store_image_file(body['id'], image_file)
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="delete_image")
@decorators.idempotent_id('3b5c341e-645b-11e6-ac4f-080027d0d606')
def test_delete_image(self):
@@ -99,7 +99,7 @@
self.client.delete_image(image_id)
self.client.wait_for_resource_deletion(image_id)
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="get_image")
@decorators.idempotent_id('3085c7c6-645b-11e6-ac4f-080027d0d606')
def test_show_image(self):
@@ -119,7 +119,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self.client.show_image(image_id)
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="get_images")
@decorators.idempotent_id('bf1a4e94-645b-11e6-ac4f-080027d0d606')
def test_list_images(self):
@@ -133,7 +133,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self.client.list_images()
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="modify_image")
@decorators.idempotent_id('32ecf48c-645e-11e6-ac4f-080027d0d606')
def test_update_image(self):
@@ -159,7 +159,7 @@
body = self.client.update_image(image_id, [
dict(replace='/name', value=new_image_name)])
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="publicize_image")
@decorators.idempotent_id('0ea4809c-6461-11e6-ac4f-080027d0d606')
def test_publicize_image(self):
@@ -175,7 +175,7 @@
disk_format='raw',
visibility='public')
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="deactivate")
@decorators.idempotent_id('b488458c-65df-11e6-9947-080027824017')
def test_deactivate_image(self):
@@ -199,7 +199,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self.client.deactivate_image(image_id)
- @rbac_rule_validation.action(component="Image", service="glance",
+ @rbac_rule_validation.action(service="glance",
rule="reactivate")
@decorators.idempotent_id('d3fa28b8-65df-11e6-9947-080027824017')
def test_reactivate_image(self):
diff --git a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
index ff7628a..77d042d 100644
--- a/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
+++ b/patrole_tempest_plugin/tests/api/network/test_networks_rbac.py
@@ -130,7 +130,7 @@
rbac_utils.switch_role(self, switchToRbacRole=False)
super(RbacNetworksTest, self).tearDown()
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="create_network")
@decorators.idempotent_id('95b9baab-1ece-4e2b-89c8-8d671d974e54')
def test_create_network(self):
@@ -142,7 +142,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self._create_network()
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="create_network:shared")
@decorators.idempotent_id('ccabf2a9-28c8-44b2-80e6-ffd65d43eef2')
def test_create_network_shared(self):
@@ -154,7 +154,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self._create_network(shared=True)
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="create_network:router:external")
@decorators.idempotent_id('51adf2a7-739c-41e0-8857-3b4c460cbd24')
def test_create_network_router_external(self):
@@ -166,7 +166,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self._create_network(router_external=True)
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="create_network:provider:network_type")
@decorators.idempotent_id('3c42f7b8-b80c-44ef-8fa4-69ec4b1836bc')
def test_create_network_provider_network_type(self):
@@ -179,7 +179,7 @@
self._create_network(provider_network_type='vxlan')
@rbac_rule_validation.action(
- component="Network", service="neutron",
+ service="neutron",
rule="create_network:provider:physical_network")
@decorators.idempotent_id('f458033b-2d52-4fd1-86db-e31e111d6fac')
def test_create_network_provider_physical_network(self):
@@ -193,7 +193,7 @@
provider_physical_network='ph-eth0')
@rbac_rule_validation.action(
- component="Network", service="neutron",
+ service="neutron",
rule="create_network:provider:segmentation_id")
@decorators.idempotent_id('b9decb7b-68ef-4504-b99b-41edbf7d2af5')
def test_create_network_provider_segmentation_id(self):
@@ -206,7 +206,7 @@
self._create_network(provider_network_type='vxlan',
provider_segmentation_id=200)
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="update_network")
@decorators.idempotent_id('6485bb4e-e110-48ae-83e1-3ec8b40c3107')
def test_update_network(self):
@@ -223,7 +223,7 @@
updated_network = self._update_network(admin=True)
self.assertEqual(updated_network['admin_state_up'], True)
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="update_network:shared")
@decorators.idempotent_id('37ea3e33-47d9-49fc-9bba-1af98fbd46d6')
def test_update_network_shared(self):
@@ -240,7 +240,7 @@
updated_network = self._update_network(shared_network=False)
self.assertEqual(updated_network['shared'], False)
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="update_network:router:external")
@decorators.idempotent_id('34884c22-499b-4960-97f1-e2ed8522a9c9')
def test_update_network_router_external(self):
@@ -257,7 +257,7 @@
updated_network = self._update_network(router_external=False)
self.assertEqual(updated_network['router:external'], False)
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="get_network")
@decorators.idempotent_id('0eb62d04-338a-4ff4-a8fa-534e52110534')
def test_show_network(self):
@@ -270,7 +270,7 @@
# show a network that has been created during class setup
self.networks_client.show_network(self.admin_network['id'])
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="get_network:router:external")
@decorators.idempotent_id('529e4814-22e9-413f-af48-8fefcd637344')
def test_show_network_router_external(self):
@@ -285,7 +285,7 @@
self.networks_client.show_network(self.admin_network['id'],
**post_body)
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="get_network:provider:network_type")
@decorators.idempotent_id('6521dd60-0950-458b-8491-09d3c84ac0f4')
def test_show_network_provider_network_type(self):
@@ -304,7 +304,7 @@
if len(showed_net) == 0:
raise rbac_exceptions.RbacActionFailed
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="get_network:provider:physical_network")
@decorators.idempotent_id('c049f11a-240c-4a85-ad43-a4d3fd0a5e39')
def test_show_network_provider_physical_network(self):
@@ -323,7 +323,7 @@
if len(showed_net) == 0:
raise rbac_exceptions.RbacActionFailed
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="get_network:provider:segmentation_id")
@decorators.idempotent_id('38d9f085-6365-4f81-bac9-c53c294d727e')
def test_show_network_provider_segmentation_id(self):
@@ -345,7 +345,7 @@
key = showed_net.get('provider:segmentation_id', "NotFound")
self.assertIsNot(key, "NotFound")
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="delete_network")
@decorators.idempotent_id('56ca50ed-ac58-49d6-b239-ed39e7124d5c')
def test_delete_network(self):
@@ -358,7 +358,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self.networks_client.delete_network(network['id'])
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="create_subnet")
@decorators.idempotent_id('44f42aaf-8a9a-4678-868a-b8fe82689554')
def test_create_subnet(self):
@@ -374,7 +374,7 @@
# Create a subnet
self.create_subnet(network, enable_dhcp=False)
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="get_subnet")
@decorators.idempotent_id('eb88be84-2465-482b-a40b-5201acb41152')
def test_show_subnet(self):
@@ -386,7 +386,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self.subnets_client.show_subnet(self.admin_subnet['id'])
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="update_subnet")
@decorators.idempotent_id('1bfeaec5-83b9-4140-8138-93a0a9d04cee')
def test_update_subnet(self):
@@ -399,7 +399,7 @@
self.subnets_client.update_subnet(self.admin_subnet['id'],
name="New_subnet")
- @rbac_rule_validation.action(component="Network", service="neutron",
+ @rbac_rule_validation.action(service="neutron",
rule="delete_subnet")
@decorators.idempotent_id('1ad1400f-dc84-4edb-9674-b33bbfb0d3e3')
def test_delete_subnet(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/admin/test_volume_quotas_rbac.py b/patrole_tempest_plugin/tests/api/volume/admin/test_volume_quotas_rbac.py
index 3aa79b7..851dec3 100644
--- a/patrole_tempest_plugin/tests/api/volume/admin/test_volume_quotas_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/admin/test_volume_quotas_rbac.py
@@ -44,7 +44,7 @@
rbac_utils.switch_role(self, switchToRbacRole=False)
super(VolumeQuotasAdminRbacTest, self).tearDown()
- @rbac_rule_validation.action(component="Volume", service="cinder",
+ @rbac_rule_validation.action(service="cinder",
rule="volume_extension:quotas:show")
@decorators.idempotent_id('b3c7177e-b6b1-4d0f-810a-fc95606964dd')
def test_list_default_quotas(self):
@@ -52,7 +52,7 @@
self.client.show_default_quota_set(
self.demo_tenant_id)['quota_set']
- @rbac_rule_validation.action(component="Volume", service="cinder",
+ @rbac_rule_validation.action(service="cinder",
rule="volume_extension:quotas:update")
@decorators.idempotent_id('60f8f421-1630-4953-b449-b22af32265c7')
def test_update_all_quota_resources_for_tenant(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_snapshots_actions_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_snapshots_actions_rbac.py
index 4af7f00..703b284 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_snapshots_actions_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_snapshots_actions_rbac.py
@@ -53,7 +53,7 @@
cls.snapshot_id = cls.snapshot['id']
@rbac_rule_validation.action(
- component="Volume", service="cinder",
+ service="cinder",
rule="volume_extension:snapshot_admin_actions:reset_status")
@decorators.idempotent_id('ea430145-34ef-408d-b678-95d5ae5f46eb')
def test_reset_snapshot_status(self):
@@ -64,7 +64,7 @@
reset_snapshot_status(self.snapshot['id'], status)
@rbac_rule_validation.action(
- component="Volume", service="cinder",
+ service="cinder",
rule="volume_extension:volume_admin_actions:force_delete")
@decorators.idempotent_id('a8b0f7d8-4c00-4645-b8d5-33ab4eecc6cb')
def test_snapshot_force_delete(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_create_delete_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_create_delete_rbac.py
index bfb2b1a..7d4fe30 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_create_delete_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_create_delete_rbac.py
@@ -41,7 +41,7 @@
volume = self.create_volume()
return volume
- @rbac_rule_validation.action(component="Volume", service="cinder",
+ @rbac_rule_validation.action(service="cinder",
rule="volume:create")
@decorators.idempotent_id('426b08ef-6394-4d06-9128-965d5a6c38ef')
def test_create_volume(self):
@@ -49,7 +49,7 @@
# Create a volume
self._create_volume()
- @rbac_rule_validation.action(component="Volume", service="cinder",
+ @rbac_rule_validation.action(service="cinder",
rule="volume:delete")
@decorators.idempotent_id('6de9f9c2-509f-4558-867b-af21c7163be4')
def test_delete_volume(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
index a9cf5e5..e266827 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volume_metadata_rbac.py
@@ -45,7 +45,7 @@
self.volumes_client.create_volume_metadata(volume['id'],
metadata)['metadata']
- @rbac_rule_validation.action(component="Volume", service="cinder",
+ @rbac_rule_validation.action(service="cinder",
rule="volume:update_volume_metadata")
@decorators.idempotent_id('232bbb8b-4c29-44dc-9077-b1398c20b738')
def test_create_volume_metadata(self):
@@ -53,7 +53,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self._add_metadata(volume)
- @rbac_rule_validation.action(component="Volume", service="cinder",
+ @rbac_rule_validation.action(service="cinder",
rule="volume:get")
@decorators.idempotent_id('87ea37d9-23ab-47b2-a59c-16fc4d2c6dfa')
def test_get_volume_metadata(self):
@@ -62,7 +62,7 @@
rbac_utils.switch_role(self, switchToRbacRole=True)
self.volumes_client.show_volume_metadata(volume['id'])['metadata']
- @rbac_rule_validation.action(component="Volume", service="cinder",
+ @rbac_rule_validation.action(service="cinder",
rule="volume:delete_volume_metadata")
@decorators.idempotent_id('7498dfc1-9db2-4423-ad20-e6dcb25d1beb')
def test_delete_volume_metadata(self):
@@ -72,7 +72,7 @@
self.volumes_client.delete_volume_metadata_item(volume['id'],
"key1")
- @rbac_rule_validation.action(component="Volume", service="cinder",
+ @rbac_rule_validation.action(service="cinder",
rule="volume:update_volume_metadata")
@decorators.idempotent_id('8ce2ff80-99ba-49ae-9bb1-7e96729ee5af')
def test_update_volume_metadata(self):
diff --git a/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py b/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py
index a4f88ea..3e21444 100644
--- a/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py
+++ b/patrole_tempest_plugin/tests/api/volume/test_volumes_rbac.py
@@ -38,7 +38,7 @@
super(VolumesRbacTest, self).tearDown()
@rbac_rule_validation.action(
- component="Volume", service="cinder",
+ service="cinder",
rule="volume_extension:volume_admin_actions:reset_status")
@decorators.idempotent_id('4b3dad7d-0e73-4839-8781-796dd3d7af1d')
def test_volume_reset_status(self):
@@ -49,7 +49,7 @@
self.client.reset_volume_status(volume['id'], status='availble')
@rbac_rule_validation.action(
- component="Volume", service="cinder",
+ service="cinder",
rule="volume_extension:volume_admin_actions:force_delete")
@decorators.idempotent_id('a312a937-6abf-4b91-a950-747086cbce48')
def test_volume_force_delete_when_volume_is_error(self):
diff --git a/tests/resources/admin_rbac_policy.json b/tests/resources/admin_rbac_policy.json
new file mode 100644
index 0000000..7828921
--- /dev/null
+++ b/tests/resources/admin_rbac_policy.json
@@ -0,0 +1,6 @@
+{
+ "admin_rule": "role:admin",
+ "is_admin_rule": "is_admin:True",
+ "alt_admin_rule": "is_admin:True or (role:admin and is_project_admin:True)",
+ "non_admin_rule": "role:Member"
+}
diff --git a/tests/custom_rbac_policy.json b/tests/resources/custom_rbac_policy.json
similarity index 100%
rename from tests/custom_rbac_policy.json
rename to tests/resources/custom_rbac_policy.json
diff --git a/tests/test_rbac_role_converter.py b/tests/test_rbac_role_converter.py
index 942d7d0..dadab88 100644
--- a/tests/test_rbac_role_converter.py
+++ b/tests/test_rbac_role_converter.py
@@ -29,7 +29,11 @@
current_directory = os.path.dirname(os.path.realpath(__file__))
self.custom_policy_file = os.path.join(current_directory,
+ 'resources',
'custom_rbac_policy.json')
+ self.admin_policy_file = os.path.join(current_directory,
+ 'resources',
+ 'admin_rbac_policy.json')
def test_custom_policy(self):
default_roles = ['zero', 'one', 'two', 'three', 'four',
@@ -37,11 +41,8 @@
CONF.set_override('rbac_roles', default_roles, group='rbac',
enforce_type=True)
- self.converter = rbac_role_converter.RbacPolicyConverter(
- "custom",
- self.custom_policy_file
- )
- self.roles_dict = self.converter.rules
+ converter = rbac_role_converter.RbacPolicyConverter(
+ None, "test", self.custom_policy_file)
expected = {
'policy_action_1': ['two', 'four', 'six', 'eight'],
@@ -55,13 +56,57 @@
fake_rule = 'fake_rule'
- self.assertFalse(fake_rule in self.roles_dict.keys())
+ for role in default_roles:
+ self.assertRaises(KeyError, converter.allowed, fake_rule, role)
- for rule in expected.keys():
- self.assertTrue(rule in self.roles_dict.keys())
- expected_roles = expected[rule]
- unexpected_roles = set(default_roles) - set(expected[rule])
- for role in expected_roles:
- self.assertTrue(role in self.roles_dict[rule])
- for role in unexpected_roles:
- self.assertFalse(role in self.roles_dict[rule])
+ for rule, role_list in expected.items():
+ for role in role_list:
+ self.assertTrue(converter.allowed(rule, role))
+ for role in set(default_roles) - set(role_list):
+ self.assertFalse(converter.allowed(rule, role))
+
+ def test_admin_policy_file_with_admin_role(self):
+ default_roles = ['admin', 'Member']
+ CONF.set_override('rbac_roles', default_roles, group='rbac',
+ enforce_type=True)
+
+ converter = rbac_role_converter.RbacPolicyConverter(
+ None, "test", self.admin_policy_file)
+
+ role = 'admin'
+ allowed_rules = [
+ 'admin_rule'
+ ]
+ disallowed_rules = [
+ 'is_admin_rule', 'alt_admin_rule', 'non_admin_rule']
+
+ for rule in allowed_rules:
+ allowed = converter.allowed(rule, role)
+ self.assertTrue(allowed)
+
+ for rule in disallowed_rules:
+ allowed = converter.allowed(rule, role)
+ self.assertFalse(allowed)
+
+ def test_admin_policy_file_with_member_role(self):
+ default_roles = ['admin', 'Member']
+ CONF.set_override('rbac_roles', default_roles, group='rbac',
+ enforce_type=True)
+
+ converter = rbac_role_converter.RbacPolicyConverter(
+ None, "test", self.admin_policy_file)
+
+ role = 'Member'
+ allowed_rules = [
+ 'non_admin_rule'
+ ]
+ disallowed_rules = [
+ 'admin_rule', 'is_admin_rule', 'alt_admin_rule']
+
+ for rule in allowed_rules:
+ allowed = converter.allowed(rule, role)
+ self.assertTrue(allowed)
+
+ for rule in disallowed_rules:
+ allowed = converter.allowed(rule, role)
+ self.assertFalse(allowed)
diff --git a/tests/test_rbac_rule_validation.py b/tests/test_rbac_rule_validation.py
index a8e2be3..edc442e 100644
--- a/tests/test_rbac_rule_validation.py
+++ b/tests/test_rbac_rule_validation.py
@@ -25,31 +25,42 @@
class RBACRuleValidationTest(base.TestCase):
@mock.patch('patrole_tempest_plugin.rbac_auth.RbacAuthority')
def test_RBAC_rv_happy_path(self, mock_auth):
- decorator = rbac_rv.action("", "", "")
+ decorator = rbac_rv.action("", "")
mock_function = mock.Mock()
+ mock_args = mock.MagicMock(**{
+ 'auth_provider.credentials.tenant_id': 'tenant_id'
+ })
wrapper = decorator(mock_function)
- wrapper()
+ wrapper((mock_args))
self.assertTrue(mock_function.called)
@mock.patch('patrole_tempest_plugin.rbac_auth.RbacAuthority')
def test_RBAC_rv_forbidden(self, mock_auth):
- decorator = rbac_rv.action("", "", "")
+ decorator = rbac_rv.action("", "")
mock_function = mock.Mock()
mock_function.side_effect = exceptions.Forbidden
wrapper = decorator(mock_function)
- self.assertRaises(exceptions.Forbidden, wrapper)
+ mock_args = mock.MagicMock(**{
+ 'auth_provider.credentials.tenant_id': 'tenant_id'
+ })
+
+ self.assertRaises(exceptions.Forbidden, wrapper, mock_args)
@mock.patch('patrole_tempest_plugin.rbac_auth.RbacAuthority')
def test_RBAC_rv_rbac_action_failed(self, mock_auth):
- decorator = rbac_rv.action("", "", "")
+ decorator = rbac_rv.action("", "")
mock_function = mock.Mock()
mock_function.side_effect = rbac_exceptions.RbacActionFailed
+ mock_args = mock.MagicMock(**{
+ 'auth_provider.credentials.tenant_id': 'tenant_id'
+ })
+
wrapper = decorator(mock_function)
- self.assertRaises(exceptions.Forbidden, wrapper)
+ self.assertRaises(exceptions.Forbidden, wrapper, mock_args)
@mock.patch('patrole_tempest_plugin.rbac_auth.RbacAuthority')
def test_RBAC_rv_not_allowed(self, mock_auth):
- decorator = rbac_rv.action("", "", "")
+ decorator = rbac_rv.action("", "")
mock_function = mock.Mock()
wrapper = decorator(mock_function)
@@ -58,25 +69,33 @@
mock_permission.get_permission.return_value = False
mock_auth.return_value = mock_permission
- self.assertRaises(rbac_exceptions.RbacOverPermission, wrapper)
+ mock_args = mock.MagicMock(**{
+ 'auth_provider.credentials.tenant_id': 'tenant_id'
+ })
+
+ self.assertRaises(rbac_exceptions.RbacOverPermission, wrapper,
+ mock_args)
@mock.patch('patrole_tempest_plugin.rbac_auth.RbacAuthority')
def test_RBAC_rv_forbidden_not_allowed(self, mock_auth):
- decorator = rbac_rv.action("", "", "")
+ decorator = rbac_rv.action("", "")
mock_function = mock.Mock()
mock_function.side_effect = exceptions.Forbidden
+ mock_args = mock.MagicMock(**{
+ 'auth_provider.credentials.tenant_id': 'tenant_id'
+ })
wrapper = decorator(mock_function)
mock_permission = mock.Mock()
mock_permission.get_permission.return_value = False
mock_auth.return_value = mock_permission
- self.assertIsNone(wrapper())
+ self.assertIsNone(wrapper(mock_args))
@mock.patch('patrole_tempest_plugin.rbac_auth.RbacAuthority')
def test_RBAC_rv_rbac_action_failed_not_allowed(self, mock_auth):
- decorator = rbac_rv.action("", "", "")
+ decorator = rbac_rv.action("", "")
mock_function = mock.Mock()
mock_function.side_effect = rbac_exceptions.RbacActionFailed
@@ -86,4 +105,8 @@
mock_permission.get_permission.return_value = False
mock_auth.return_value = mock_permission
- self.assertIsNone(wrapper())
+ mock_args = mock.MagicMock(**{
+ 'auth_provider.credentials.tenant_id': 'tenant_id'
+ })
+
+ self.assertIsNone(wrapper(mock_args))