예제 #1
0
    def _validate_switch_role(self, test_obj, toggle_rbac_role):
        """Validates that the rbac role passed to `switch_role` is legal.

        Throws an error for the following improper usages of `switch_role`:
            * `switch_role` is not called with a boolean value
            * `switch_role` is never called in a test file, except in tearDown
            * `switch_role` is called with the same boolean value twice
        """
        if not isinstance(toggle_rbac_role, bool):
            raise rbac_exceptions.RbacResourceSetupFailed(
                'toggle_rbac_role must be a boolean value.')

        # The unique key is the combination of module path plus class name.
        class_name = test_obj.__name__ if isinstance(test_obj, type) else \
            test_obj.__class__.__name__
        module_name = test_obj.__module__
        key = '%s.%s' % (module_name, class_name)

        self.switch_role_history.setdefault(key, None)

        if self.switch_role_history[key] == toggle_rbac_role:
            # If an exception was thrown, like a skipException or otherwise,
            # then this is a legitimate reason why `switch_role` was not
            # called, so only raise an exception if no current exception is
            # being handled.
            if sys.exc_info()[0] is None:
                self.switch_role_history[key] = False
                error_message = '`toggle_rbac_role` must not be called with '\
                    'the same bool value twice. Make sure that you included '\
                    'a rbac_utils.switch_role method call inside the test.'
                LOG.error(error_message)
                raise rbac_exceptions.RbacResourceSetupFailed(error_message)
        else:
            self.switch_role_history[key] = toggle_rbac_role
예제 #2
0
def _is_authorized(test_obj, service, rule_name, extra_target_data):
    try:
        project_id = test_obj.auth_provider.credentials.project_id
        user_id = test_obj.auth_provider.credentials.user_id
    except AttributeError as e:
        msg = ("{0}: project_id/user_id not found in "
               "cls.auth_provider.credentials".format(e))
        LOG.error(msg)
        raise rbac_exceptions.RbacResourceSetupFailed(msg)

    try:
        role = CONF.rbac.rbac_test_role
        formatted_target_data = _format_extra_target_data(
            test_obj, extra_target_data)
        policy_parser = rbac_policy_parser.RbacPolicyParser(
            project_id,
            user_id,
            service,
            extra_target_data=formatted_target_data)
        is_allowed = policy_parser.allowed(rule_name, role)

        if is_allowed:
            LOG.debug("[Action]: %s, [Role]: %s is allowed!", rule_name, role)
        else:
            LOG.debug("[Action]: %s, [Role]: %s is NOT allowed!", rule_name,
                      role)
        return is_allowed
    except rbac_exceptions.RbacParsingException as e:
        if CONF.rbac.strict_policy_check:
            raise e
        else:
            raise testtools.TestCase.skipException(str(e))
    return False
예제 #3
0
        def wrapper(*args, **kwargs):
            if args and isinstance(args[0], test.BaseTestCase):
                test_obj = args[0]
            else:
                raise rbac_exceptions.RbacResourceSetupFailed(
                    '`rbac_rule_validation` decorator can only be applied to '
                    'an instance of `tempest.test.BaseTestCase`.')

            allowed = _is_authorized(test_obj, service, rule,
                                     extra_target_data, admin_only)

            expected_exception, irregular_msg = _get_exception_type(
                expected_error_code)

            test_status = 'Allowed'

            try:
                test_func(*args, **kwargs)
            except rbac_exceptions.RbacInvalidService as e:
                msg = ("%s is not a valid service." % service)
                test_status = ('Error, %s' % (msg))
                LOG.error(msg)
                raise exceptions.NotFound("%s RbacInvalidService was: %s" %
                                          (msg, e))
            except (expected_exception,
                    rbac_exceptions.RbacConflictingPolicies,
                    rbac_exceptions.RbacMalformedResponse) as e:
                test_status = 'Denied'
                if irregular_msg:
                    LOG.warning(irregular_msg.format(rule, service))
                if allowed:
                    msg = ("Role %s was not allowed to perform %s." %
                           (role, rule))
                    LOG.error(msg)
                    raise exceptions.Forbidden("%s Exception was: %s" %
                                               (msg, e))
            except Exception as e:
                with excutils.save_and_reraise_exception():
                    exc_info = sys.exc_info()
                    error_details = six.text_type(exc_info[1])
                    msg = ("An unexpected exception has occurred during test: "
                           "%s. Exception was: %s" %
                           (test_func.__name__, error_details))
                    test_status = 'Error, %s' % (error_details)
                    LOG.error(msg)
            else:
                if not allowed:
                    LOG.error("Role %s was allowed to perform %s", role, rule)
                    raise rbac_exceptions.RbacOverPermission(
                        "OverPermission: Role %s was allowed to perform %s" %
                        (role, rule))
            finally:
                test_obj.rbac_utils.switch_role(test_obj,
                                                toggle_rbac_role=False)
                if CONF.patrole_log.enable_reporting:
                    RBACLOG.info(
                        "[Service]: %s, [Test]: %s, [Rule]: %s, "
                        "[Expected]: %s, [Actual]: %s", service,
                        test_func.__name__, rule,
                        "Allowed" if allowed else "Denied", test_status)
예제 #4
0
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
예제 #5
0
        def wrapper(*args, **kwargs):
            if args and isinstance(args[0], test.BaseTestCase):
                test_obj = args[0]
            else:
                raise rbac_exceptions.RbacResourceSetupFailed(
                    '`rbac_rule_validation` decorator can only be applied to '
                    'an instance of `tempest.test.BaseTestCase`.')

            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))
                allowed = test_obj.rbac_utils.is_admin
            else:
                allowed = _is_authorized(test_obj, service, rule,
                                         extra_target_data)

            expected_exception, irregular_msg = _get_exception_type(
                expected_error_code)

            try:
                func(*args, **kwargs)
            except rbac_exceptions.RbacInvalidService as e:
                msg = ("%s is not a valid service." % service)
                LOG.error(msg)
                raise exceptions.NotFound("%s RbacInvalidService was: %s" %
                                          (msg, e))
            except (expected_exception, rbac_exceptions.RbacActionFailed) as e:
                if irregular_msg:
                    LOG.warning(irregular_msg.format(rule, service))
                if allowed:
                    msg = ("Role %s was not allowed to perform %s." %
                           (role, rule))
                    LOG.error(msg)
                    raise exceptions.Forbidden("%s Exception was: %s" %
                                               (msg, e))
            except Exception as e:
                exc_info = sys.exc_info()
                error_details = exc_info[1].__str__()
                msg = ("%s An unexpected exception has occurred: Expected "
                       "exception was %s, which was not thrown." %
                       (error_details, expected_exception.__name__))
                LOG.error(msg)
                six.reraise(exc_info[0], exc_info[0](msg), exc_info[2])
            else:
                if not allowed:
                    LOG.error("Role %s was allowed to perform %s", role, rule)
                    raise rbac_exceptions.RbacOverPermission(
                        "OverPermission: Role %s was allowed to perform %s" %
                        (role, rule))
            finally:
                test_obj.rbac_utils.switch_role(test_obj,
                                                toggle_rbac_role=False)
예제 #6
0
    def _get_roles_by_name(self):
        available_roles = self.admin_roles_client.list_roles()
        admin_role_id = rbac_role_id = None

        for role in available_roles['roles']:
            if role['name'] == CONF.patrole.rbac_test_role:
                rbac_role_id = role['id']
            if role['name'] == CONF.identity.admin_role:
                admin_role_id = role['id']

        if not all([admin_role_id, rbac_role_id]):
            msg = ("Roles defined by `[patrole] rbac_test_role` and "
                   "`[identity] admin_role` must be defined in the system.")
            raise rbac_exceptions.RbacResourceSetupFailed(msg)

        self.admin_role_id = admin_role_id
        self.rbac_role_id = rbac_role_id
예제 #7
0
    def _get_roles(self):
        available_roles = self.roles_client.list_roles()
        admin_role_id = rbac_role_id = None

        for role in available_roles['roles']:
            if role['name'] == CONF.rbac.rbac_test_role:
                rbac_role_id = role['id']
            if role['name'] == CONF.identity.admin_role:
                admin_role_id = role['id']

        if not admin_role_id or not rbac_role_id:
            msg = "Role with name 'admin' does not exist in the system."\
                if not admin_role_id else "Role defined by rbac_test_role "\
                "does not exist in the system."
            raise rbac_exceptions.RbacResourceSetupFailed(msg)

        self.admin_role_id = admin_role_id
        self.rbac_role_id = rbac_role_id
예제 #8
0
    def switch_role(self, test_obj, toggle_rbac_role=False):
        self.user_id = test_obj.auth_provider.credentials.user_id
        self.project_id = test_obj.auth_provider.credentials.tenant_id
        self.token = test_obj.auth_provider.get_token()
        self.identity_version = test_obj.get_identity_version()

        if not credentials.is_admin_available(
                identity_version=self.identity_version):
            msg = "Missing Identity Admin API credentials in configuration."
            raise rbac_exceptions.RbacResourceSetupFailed(msg)

        self.roles_client = test_obj.os_admin.roles_v3_client

        LOG.debug('Switching role to: %s', toggle_rbac_role)

        try:
            if not self.admin_role_id or not self.rbac_role_id:
                self._get_roles()

            self._validate_switch_role(test_obj, toggle_rbac_role)

            if toggle_rbac_role:
                self._add_role_to_user(self.rbac_role_id)
            else:
                self._add_role_to_user(self.admin_role_id)
        except Exception as exp:
            LOG.error(exp)
            raise
        finally:
            # NOTE(felipemonteiro): These two comments below are copied from
            # tempest.api.identity.v2/v3.test_users.
            #
            # Reset auth again to verify the password restore does work.
            # Clear auth restores the original credentials and deletes
            # cached auth data.
            test_obj.auth_provider.clear_auth()
            # Fernet tokens are not subsecond aware and Keystone should only be
            # precise to the second. Sleep to ensure we are passing the second
            # boundary before attempting to authenticate. If token is of type
            # uuid, then do not sleep.
            if not uuid_utils.is_uuid_like(self.token):
                time.sleep(1)
            test_obj.auth_provider.set_auth()
예제 #9
0
    def _init_roles(cls):
        available_roles = cls.admin_roles_client.list_roles()['roles']
        cls._role_map = {r['name']: r['id'] for r in available_roles}
        LOG.debug('Available roles: %s', cls._role_map.keys())

        rbac_role_ids = []
        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)

        for role_name in roles:
            rbac_role_ids.append(cls._role_map.get(role_name))

        admin_role_id = cls._role_map.get(CONF.identity.admin_role)

        if not all([admin_role_id, all(rbac_role_ids)]):
            missing_roles = []
            msg = ("Could not find `[patrole] rbac_test_roles` or "
                   "`[identity] admin_role`, both of which are required for "
                   "RBAC testing.")
            if not admin_role_id:
                missing_roles.append(CONF.identity.admin_role)
            if not all(rbac_role_ids):
                missing_roles += [
                    role_name for role_name in roles
                    if role_name not in cls._role_map
                ]

            msg += " Following roles were not found: %s." % (
                ", ".join(missing_roles))
            msg += " Available roles: %s." % ", ".join(cls._role_map)
            raise rbac_exceptions.RbacResourceSetupFailed(msg)

        cls._admin_role_id = admin_role_id
        cls._rbac_role_ids = rbac_role_ids
        # Adding backward mapping
        cls._role_map.update({v: k for k, v in cls._role_map.items()})
예제 #10
0
    def _get_roles_by_name(self):
        available_roles = self.admin_roles_client.list_roles()['roles']
        role_map = {r['name']: r['id'] for r in available_roles}
        LOG.debug('Available roles: %s', list(role_map.keys()))

        admin_role_id = role_map.get(CONF.identity.admin_role)
        rbac_role_id = role_map.get(CONF.patrole.rbac_test_role)

        if not all([admin_role_id, rbac_role_id]):
            missing_roles = []
            msg = ("Could not find `[patrole] rbac_test_role` or "
                   "`[identity] admin_role`, both of which are required for "
                   "RBAC testing.")
            if not admin_role_id:
                missing_roles.append(CONF.identity.admin_role)
            if not rbac_role_id:
                missing_roles.append(CONF.patrole.rbac_test_role)
            msg += " Following roles were not found: %s." % (
                ", ".join(missing_roles))
            msg += " Available roles: %s." % ", ".join(list(role_map.keys()))
            raise rbac_exceptions.RbacResourceSetupFailed(msg)

        self.admin_role_id = admin_role_id
        self.rbac_role_id = rbac_role_id
예제 #11
0
        def wrapper(*args, **kwargs):
            if args and isinstance(args[0], test.BaseTestCase):
                test_obj = args[0]
            else:
                raise rbac_exceptions.RbacResourceSetupFailed(
                    '`rbac_rule_validation` decorator can only be applied to '
                    'an instance of `tempest.test.BaseTestCase`.')

            allowed = True
            disallowed_rules = []
            for rule in rules:
                _allowed = _is_authorized(test_obj, service, rule,
                                          extra_target_data)
                if not _allowed:
                    disallowed_rules.append(rule)
                allowed = allowed and _allowed

            exp_error_code = expected_error_code
            if disallowed_rules:
                # Choose the first disallowed rule and expect the error
                # code corresponding to it.
                first_error_index = rules.index(disallowed_rules[0])
                exp_error_code = expected_error_codes[first_error_index]
                LOG.debug("%s: Expecting %d to be raised for policy name: %s",
                          test_func.__name__, exp_error_code,
                          disallowed_rules[0])

            expected_exception, irregular_msg = _get_exception_type(
                exp_error_code)

            caught_exception = None
            test_status = 'Allowed'

            try:
                test_func(*args, **kwargs)
            except rbac_exceptions.RbacInvalidServiceException:
                with excutils.save_and_reraise_exception():
                    msg = ("%s is not a valid service." % service)
                    # FIXME(felipemonteiro): This test_status is logged too
                    # late. Need a function to log it before re-raising.
                    test_status = ('Error, %s' % (msg))
                    LOG.error(msg)
            except (expected_exception,
                    rbac_exceptions.RbacConflictingPolicies,
                    rbac_exceptions.RbacMalformedResponse) as actual_exception:
                caught_exception = actual_exception
                test_status = 'Denied'

                if irregular_msg:
                    LOG.warning(irregular_msg, test_func.__name__,
                                ', '.join(rules), service)

                if allowed:
                    msg = ("Role %s was not allowed to perform the following "
                           "actions: %s. Expected allowed actions: %s. "
                           "Expected disallowed actions: %s." %
                           (role, sorted(rules),
                            sorted(set(rules) - set(disallowed_rules)),
                            sorted(disallowed_rules)))
                    LOG.error(msg)
                    raise rbac_exceptions.RbacUnderPermissionException(
                        "%s Exception was: %s" % (msg, actual_exception))
            except Exception as actual_exception:
                caught_exception = actual_exception

                if _check_for_expected_mismatch_exception(
                        expected_exception, actual_exception):
                    LOG.error(
                        'Expected and actual exceptions do not match. '
                        'Expected: %s. Actual: %s.', expected_exception,
                        actual_exception.__class__)
                    raise rbac_exceptions.RbacExpectedWrongException(
                        expected=expected_exception,
                        actual=actual_exception.__class__,
                        exception=actual_exception)
                else:
                    with excutils.save_and_reraise_exception():
                        exc_info = sys.exc_info()
                        error_details = six.text_type(exc_info[1])
                        msg = ("An unexpected exception has occurred during "
                               "test: %s. Exception was: %s" %
                               (test_func.__name__, error_details))
                        test_status = 'Error, %s' % (error_details)
                        LOG.error(msg)
            else:
                if not allowed:
                    msg = (
                        "OverPermission: Role %s was allowed to perform the "
                        "following disallowed actions: %s" %
                        (role, sorted(disallowed_rules)))
                    LOG.error(msg)
                    raise rbac_exceptions.RbacOverPermissionException(msg)
            finally:
                if CONF.patrole_log.enable_reporting:
                    RBACLOG.info(
                        "[Service]: %s, [Test]: %s, [Rules]: %s, "
                        "[Expected]: %s, [Actual]: %s", service,
                        test_func.__name__, ', '.join(rules),
                        "Allowed" if allowed else "Denied", test_status)

                # Sanity-check that ``override_role`` was called to eliminate
                # false-positives and bad test flows resulting from exceptions
                # getting raised too early, too late or not at all, within
                # the scope of an RBAC test.
                _validate_override_role_called(
                    test_obj, actual_exception=caught_exception)
예제 #12
0
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