def _test_validate_service(self, v2_services, v3_services, expected_failure=False, expected_services=None): with mock.patch.object(policy_authority.PolicyAuthority, 'os_admin', autospec=True) as m_admin: m_admin.identity_services_client.list_services.\ return_value = v2_services m_admin.identity_services_v3_client.list_services.\ return_value = v3_services test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id mock_os = self.patchobject(policy_authority, 'os') mock_os.path.join.return_value = self.admin_policy_file if not expected_services: expected_services = [s['name'] for s in self.services['services']] # Guarantee a blank slate for this test. if hasattr(policy_authority.PolicyAuthority, 'available_services'): delattr(policy_authority.PolicyAuthority, 'available_services') if expected_failure: policy_parser = None expected_exception = 'invalid_service is NOT a valid service' with self.assertRaisesRegex( rbac_exceptions.RbacInvalidServiceException, expected_exception): policy_authority.PolicyAuthority(test_tenant_id, test_user_id, "INVALID_SERVICE") else: policy_parser = policy_authority.PolicyAuthority( test_tenant_id, test_user_id, "tenant_rbac_policy") # Check that the attribute is available at object and class levels. # If initialization failed, only check at class level. if policy_parser: self.assertTrue(hasattr(policy_parser, 'available_services')) self.assertEqual(expected_services, policy_parser.available_services) self.assertTrue( hasattr(policy_authority.PolicyAuthority, 'available_services')) self.assertEqual(expected_services, policy_authority.PolicyAuthority.available_services)
def test_get_rules_from_file_and_from_code(self, mock_stevedore): fake_policy_rules = self._get_fake_policies({ 'code_policy_action_1': 'rule:code_rule_1', 'code_policy_action_2': 'rule:code_rule_2', 'code_policy_action_3': 'rule:code_rule_3', }) mock_manager = mock.Mock(obj=fake_policy_rules, __name__='foo') mock_manager.configure_mock(name='tenant_rbac_policy') mock_stevedore.named.NamedExtensionManager.return_value = [ mock_manager ] test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority( test_tenant_id, test_user_id, "tenant_rbac_policy") rules = authority.get_rules() self.assertIsInstance(rules, policy_authority.policy.Rules) actual_policy_data = {k: str(v) for k, v in rules.items()} expected_policy_data = { "code_policy_action_1": "rule:code_rule_1", "code_policy_action_2": "rule:code_rule_2", "code_policy_action_3": "rule:code_rule_3", "rule1": "tenant_id:%(network:tenant_id)s", "rule2": "tenant_id:%(tenant_id)s", "rule3": "project_id:%(project_id)s", "rule4": "user_id:%(user_id)s", "admin_tenant_rule": "(role:admin and tenant_id:%(tenant_id)s)", "admin_user_rule": "(role:admin and user_id:%(user_id)s)" } self.assertEqual(expected_policy_data, actual_policy_data)
def test_alt_admin_policy_file_with_context_is_admin(self): test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority(test_tenant_id, test_user_id, "alt_admin_rbac_policy") role = 'fake_admin' allowed_rules = ['non_admin_rule'] disallowed_rules = ['admin_rule'] for rule in allowed_rules: allowed = authority.allowed(rule, role) self.assertTrue(allowed) for rule in disallowed_rules: allowed = authority.allowed(rule, role) self.assertFalse(allowed) role = 'super_admin' allowed_rules = ['admin_rule'] disallowed_rules = ['non_admin_rule'] for rule in allowed_rules: allowed = authority.allowed(rule, role) self.assertTrue(allowed) for rule in disallowed_rules: allowed = authority.allowed(rule, role) self.assertFalse(allowed)
def _test_custom_multi_roles_policy(self, *args): default_roles = ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'] test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority( test_tenant_id, test_user_id, "custom_rbac_policy") expected = { 'policy_action_1': ['two', 'four', 'six', 'eight'], 'policy_action_2': ['one', 'three', 'five', 'seven', 'nine'], 'policy_action_4': ['one', 'two', 'three', 'five', 'seven'], 'policy_action_5': ['zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine'], } for rule, role_list in expected.items(): allowed_roles_lists = [roles for roles in [ role_list[len(role_list) // 2:], role_list[:len(role_list) // 2]] if roles] for test_roles in allowed_roles_lists: self.assertTrue(authority.allowed(rule, test_roles)) disallowed_roles = list(set(default_roles) - set(role_list)) disallowed_roles_lists = [roles for roles in [ disallowed_roles[len(disallowed_roles) // 2:], disallowed_roles[:len(disallowed_roles) // 2]] if roles] for test_roles in disallowed_roles_lists: self.assertFalse(authority.allowed(rule, test_roles))
def test_discover_policy_files_with_many_invalid_one_valid( self, m_os, m_creds, *args): # Only the 3rd path is valid. m_os.path.isfile.side_effect = [False, False, True, False] # Ensure the outer for loop runs only once in `discover_policy_files`. m_creds.Manager().identity_services_v3_client.\ list_services.return_value = { 'services': [{'name': 'test_service'}]} # The expected policy will be 'baz/test_service'. self.useFixture( fixtures.ConfPatcher( custom_policy_files=['foo/%s', 'bar/%s', 'baz/%s'], group='patrole')) policy_parser = policy_authority.PolicyAuthority( None, None, 'test_service') # Ensure that "policy_files" is set at class and instance levels. self.assertIn('policy_files', dir(policy_authority.PolicyAuthority)) self.assertIn('policy_files', dir(policy_parser)) self.assertIn('test_service', policy_parser.policy_files) self.assertEqual('baz/test_service', policy_parser.policy_files['test_service'])
def test_discover_policy_files_with_many_invalid_one_valid( self, m_glob, m_os, m_admin, *args): service = 'test_service' custom_policy_files = ['foo/%s', 'bar/%s', 'baz/%s'] m_glob.iglob.side_effect = [ iter([path % service]) for path in custom_policy_files ] # Only the 3rd path is valid. m_os.path.isfile.side_effect = [False, False, True] # Ensure the outer for loop runs only once in `discover_policy_files`. m_admin.identity_services_v3_client.\ list_services.return_value = { 'services': [{'name': service}]} # The expected policy will be 'baz/test_service'. self.useFixture( fixtures.ConfPatcher(custom_policy_files=custom_policy_files, group='patrole')) policy_parser = policy_authority.PolicyAuthority(None, None, service) # Ensure that "policy_files" is set at class and instance levels. self.assertTrue( hasattr(policy_authority.PolicyAuthority, 'policy_files')) self.assertTrue(hasattr(policy_parser, 'policy_files')) self.assertEqual(['baz/%s' % service], policy_parser.policy_files[service])
def test_custom_policy(self, m_log): default_roles = [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine' ] test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority(test_tenant_id, test_user_id, "custom_rbac_policy") expected = { 'policy_action_1': ['two', 'four', 'six', 'eight'], 'policy_action_2': ['one', 'three', 'five', 'seven', 'nine'], 'policy_action_3': ['zero'], 'policy_action_4': ['one', 'two', 'three', 'five', 'seven'], 'policy_action_5': [ 'zero', 'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine' ], 'policy_action_6': ['eight'], } for rule, role_list in expected.items(): for role in role_list: self.assertTrue(authority.allowed(rule, role)) for role in set(default_roles) - set(role_list): self.assertFalse(authority.allowed(rule, role))
def _is_authorized(test_obj, service, rule, extra_target_data): """Validates whether current RBAC role has permission to do policy action. :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``. :param service: The OpenStack service that enforces ``rule``. :param rule: The name of the policy action. Examples include "identity:create_user" or "os_compute_api:os-agents". :param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic check names, whose values are string literals that reference nested ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for performing matching against attributes that are sent along with the API calls. :returns: True if the current RBAC role can perform the policy action, else False. :raises RbacResourceSetupFailed: If `project_id` or `user_id` are missing from the `auth_provider` attribute in `test_obj`. """ try: project_id = test_obj.os_primary.credentials.project_id user_id = test_obj.os_primary.credentials.user_id except AttributeError as e: msg = ("{0}: project_id or user_id not found in os_primary.credentials" .format(e)) LOG.error(msg) raise rbac_exceptions.RbacResourceSetupFailed(msg) roles = CONF.patrole.rbac_test_roles # TODO(vegasq) drop once CONF.patrole.rbac_test_role is removed if CONF.patrole.rbac_test_role: if not roles: roles.append(CONF.patrole.rbac_test_role) # Adding implied roles roles = test_obj.get_all_needed_roles(roles) # Test RBAC against custom requirements. Otherwise use oslo.policy. if CONF.patrole.test_custom_requirements: authority = requirements_authority.RequirementsAuthority( CONF.patrole.custom_requirements_file, service) else: formatted_target_data = _format_extra_target_data( test_obj, extra_target_data) authority = policy_authority.PolicyAuthority( project_id, user_id, service, extra_target_data=formatted_target_data) is_allowed = authority.allowed(rule, roles) if is_allowed: LOG.debug("[Policy action]: %s, [Role]: %s is allowed!", rule, roles) else: LOG.debug("[Policy action]: %s, [Role]: %s is NOT allowed!", rule, roles) return is_allowed
def test_tenant_user_policy(self): """Test whether rules with format tenant_id/user_id formatting work. Test whether Neutron rules that contain project_id, tenant_id, and network:tenant_id pass. And test whether Nova rules that contain user_id pass. """ test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority(test_tenant_id, test_user_id, "tenant_rbac_policy") # Check whether Member role can perform expected actions. allowed_rules = ['rule1', 'rule2', 'rule3', 'rule4'] for rule in allowed_rules: allowed = authority.allowed(rule, 'Member') self.assertTrue(allowed) disallowed_rules = ['admin_tenant_rule', 'admin_user_rule'] for disallowed_rule in disallowed_rules: self.assertFalse(authority.allowed(disallowed_rule, 'Member')) # Check whether admin role can perform expected actions. allowed_rules.extend(disallowed_rules) for rule in allowed_rules: allowed = authority.allowed(rule, 'admin') self.assertTrue(allowed) # Check whether _try_rule is called with the correct target dictionary. with mock.patch.object( authority, '_try_rule', return_value=True, autospec=True) \ as mock_try_rule: expected_target = { "project_id": mock.sentinel.tenant_id, "tenant_id": mock.sentinel.tenant_id, "network:tenant_id": mock.sentinel.tenant_id, "user_id": mock.sentinel.user_id } expected_access_data = { "roles": ['Member'], "is_admin": False, "is_admin_project": True, "user_id": mock.sentinel.user_id, "tenant_id": mock.sentinel.tenant_id, "project_id": mock.sentinel.tenant_id } for rule in allowed_rules: allowed = authority.allowed(rule, 'Member') self.assertTrue(allowed) mock_try_rule.assert_called_once_with(rule, expected_target, expected_access_data, mock.ANY) mock_try_rule.reset_mock()
def test_discover_policy_files(self): policy_parser = policy_authority.PolicyAuthority( None, None, 'tenant_rbac_policy') # Ensure that "policy_files" is set at class and instance levels. self.assertIn('policy_files', dir(policy_authority.PolicyAuthority)) self.assertIn('policy_files', dir(policy_parser)) self.assertIn('tenant_rbac_policy', policy_parser.policy_files) self.assertEqual(self.conf_policy_path % 'tenant_rbac_policy', policy_parser.policy_files['tenant_rbac_policy'])
def test_invalid_policy_rule_throws_rbac_parsing_exception(self, m_log): test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority( test_tenant_id, test_user_id, "custom_rbac_policy") fake_rule = 'fake_rule' expected_message = "Policy action: {0} not found in policy file: {1}."\ .format(fake_rule, self.custom_policy_file) e = self.assertRaises(rbac_exceptions.RbacParsingException, authority.allowed, fake_rule, None) self.assertIn(expected_message, str(e)) m_log.debug.assert_called_once_with(expected_message)
def test_invalid_policy_rule_throws_rbac_parsing_exception(self, m_log): test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority( test_tenant_id, test_user_id, "custom_rbac_policy") fake_rule = 'fake_rule' expected_message = ( 'Policy action "{0}" not found in policy files: {1} or among ' 'registered policy in code defaults for {2} service.').format( fake_rule, [self.custom_policy_file], "custom_rbac_policy") e = self.assertRaises(rbac_exceptions.RbacParsingException, authority.allowed, fake_rule, [None]) self.assertIn(expected_message, str(e)) m_log.debug.assert_called_once_with(expected_message)
def test_unknown_exception_throws_rbac_parsing_exception(self, m_log): test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority( test_tenant_id, test_user_id, "custom_rbac_policy") authority.rules = mock.MagicMock( __name__='foo', **{'__getitem__.return_value.side_effect': Exception( mock.sentinel.error)}) expected_message = "Policy action: {0} not found in "\ "policy file: {1}.".format(mock.sentinel.rule, self.custom_policy_file) e = self.assertRaises(rbac_exceptions.RbacParsingException, authority.allowed, mock.sentinel.rule, None) self.assertIn(expected_message, str(e)) m_log.debug.assert_called_once_with(expected_message)
def _test_policy_file(self, roles, allowed_rules, disallowed_rules, authority=None, service=None): if authority is None: self.assertIsNotNone(service) test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority( test_tenant_id, test_user_id, service) roles = self.test_obj.get_all_needed_roles(roles) for rule in allowed_rules: allowed = authority.allowed(rule, roles) self.assertTrue(allowed) for rule in disallowed_rules: allowed = authority.allowed(rule, roles) self.assertFalse(allowed) return authority
def test_unknown_exception_throws_rbac_parsing_exception(self, m_log): test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority( test_tenant_id, test_user_id, "custom_rbac_policy") authority.rules = mock.MagicMock( __name__='foo', **{'__getitem__.return_value.side_effect': Exception( mock.sentinel.error)}) expected_message = ( 'Policy action "[{0}]" not found in policy files: {1} or among ' 'registered policy in code defaults for {2} service.').format( mock.sentinel.rule, [self.custom_policy_file], "custom_rbac_policy") e = self.assertRaises(rbac_exceptions.RbacParsingException, authority.allowed, [mock.sentinel.rule], [None]) self.assertIn(expected_message, str(e)) m_log.debug.assert_called_once_with(expected_message)
def test_get_policy_data_from_file_and_from_code(self, mock_stevedore): fake_policy_rules = [ self._get_fake_policy_rule('code_policy_action_1', 'rule:code_rule_1'), self._get_fake_policy_rule('code_policy_action_2', 'rule:code_rule_2'), self._get_fake_policy_rule('code_policy_action_3', 'rule:code_rule_3'), ] mock_manager = mock.Mock(obj=fake_policy_rules, __name__='foo') mock_manager.configure_mock(name='fake_service') mock_stevedore.named.NamedExtensionManager.return_value = [ mock_manager ] test_tenant_id = mock.sentinel.tenant_id test_user_id = mock.sentinel.user_id authority = policy_authority.PolicyAuthority(test_tenant_id, test_user_id, "tenant_rbac_policy") policy_data = authority._get_policy_data('fake_service') self.assertIsInstance(policy_data, str) actual_policy_data = json.loads(policy_data) expected_policy_data = { "code_policy_action_1": "rule:code_rule_1", "code_policy_action_2": "rule:code_rule_2", "code_policy_action_3": "rule:code_rule_3", "rule1": "tenant_id:%(network:tenant_id)s", "rule2": "tenant_id:%(tenant_id)s", "rule3": "project_id:%(project_id)s", "rule4": "user_id:%(user_id)s", "admin_tenant_rule": "role:admin and tenant_id:%(tenant_id)s", "admin_user_rule": "role:admin and user_id:%(user_id)s" } self.assertEqual(expected_policy_data, actual_policy_data)
def _is_authorized(test_obj, service, rule, extra_target_data, admin_only): """Validates whether current RBAC role has permission to do policy action. :param test_obj: An instance or subclass of ``tempest.test.BaseTestCase``. :param service: The OpenStack service that enforces ``rule``. :param rule: The name of the policy action. Examples include "identity:create_user" or "os_compute_api:os-agents". :param extra_target_data: Dictionary, keyed with ``oslo.policy`` generic check names, whose values are string literals that reference nested ``tempest.test.BaseTestCase`` attributes. Used by ``oslo.policy`` for performing matching against attributes that are sent along with the API calls. :param admin_only: Skips over ``oslo.policy`` check because the policy action defined by ``rule`` is not enforced by the service's policy enforcement engine. For example, Keystone v2 performs an admin check for most of its endpoints. If True, ``rule`` is effectively ignored. :returns: True if the current RBAC role can perform the policy action, else False. :raises RbacResourceSetupFailed: If `project_id` or `user_id` are missing from the `auth_provider` attribute in `test_obj`. :raises RbacParsingException: if ``[patrole] strict_policy_check`` is True and the ``rule`` does not exist in the system. :raises skipException: If ``[patrole] strict_policy_check`` is False and the ``rule`` does not exist in the system. """ if admin_only: LOG.info("As admin_only is True, only admin role should be " "allowed to perform the API. Skipping oslo.policy " "check for policy action {0}.".format(rule)) return rbac_utils.is_admin() try: project_id = test_obj.os_primary.credentials.project_id user_id = test_obj.os_primary.credentials.user_id except AttributeError as e: msg = ("{0}: project_id or user_id not found in os_primary.credentials" .format(e)) LOG.error(msg) raise rbac_exceptions.RbacResourceSetupFailed(msg) try: role = CONF.patrole.rbac_test_role # Test RBAC against custom requirements. Otherwise use oslo.policy. if CONF.patrole.test_custom_requirements: authority = requirements_authority.RequirementsAuthority( CONF.patrole.custom_requirements_file, service) else: formatted_target_data = _format_extra_target_data( test_obj, extra_target_data) authority = policy_authority.PolicyAuthority( project_id, user_id, service, extra_target_data=formatted_target_data) is_allowed = authority.allowed(rule, role) if is_allowed: LOG.debug("[Action]: %s, [Role]: %s is allowed!", rule, role) else: LOG.debug("[Action]: %s, [Role]: %s is NOT allowed!", rule, role) return is_allowed except rbac_exceptions.RbacParsingException as e: if CONF.patrole.strict_policy_check: raise e else: raise testtools.TestCase.skipException(str(e)) return False