openstack的 oslo_policy的权限验证简单分析

       在openstack中,权限校验的是通过oslo_policy的进行权限的校验,本文主要基于 newton版本的olso_policy进行简单的说明。

      由于能力有限,出错在所难免,望大家指正。

policy配置文件内容

{
    "context_is_admin": "role:admin",
    "admin_or_owner":  "is_admin:True or project_id:%(project_id)s",
    "default": "rule:admin_or_owner",
    "admin_api": "is_admin:True",
    "services:get_all": "rule:admin_api",
    "test:get_all": "rule:context_is_admin"

}

校验流程

本文只是抽出在openstack使用中的权限校验的部分代码,来说明,请以具体的代码为准(比如nova,cinder)。

from oslo_policy import policy
enforcer = policy.Enforcer(CONF, policy_file=None, rules=None, default_rule=None, use_conf=True, overwrite=True)
target = {'project_id': context.project_id, 'user_id': context.user_id}
# add_policy_attributes(target)
rule = "test:test"
target = {'project_id': 'admin', 'user_id': 'admin'}
credentials = {
    'project_domain': None,
    'project_id': None,
    'user_id': None,
    'show_deleted': False,
    'roles': ['admin'],
}
return enforcer.enforce(rule, target, credentials, do_raise=True)


其中enforce中即为核心的校验代码,也为我们文中主要的流程。

enforce的校验代码

校验

代码位于/usr/lib/python2.7/site-packages/oslo_policy/policy.py

def enforce(self, rule, target, creds, do_raise=False,
            exc=None, *args, **kwargs):
    """Checks authorization of a rule against the target and credentials.

    :param rule: The rule to evaluate.
    :type rule: string or :class:`BaseCheck`
    :param dict target: As much information about the object being operated
                        on as possible.
    :param dict creds: As much information about the user performing the
                       action as possible.
    :param do_raise: Whether to raise an exception or not if check
                    fails.
    :param exc: Class of the exception to raise if the check fails.
                Any remaining arguments passed to :meth:`enforce` (both
                positional and keyword arguments) will be passed to
                the exception class. If not specified,
                :class:`PolicyNotAuthorized` will be used.

    :return: ``False`` if the policy does not allow the action and `exc` is
             not provided; otherwise, returns a value that evaluates to
             ``True``.  Note: for rules using the "case" expression, this
             ``True`` value will be the specified string from the
             expression.
    """

    self.load_rules()

    # Allow the rule to be a Check tree
    print self.rules
    if isinstance(rule, _checks.BaseCheck):
        result = rule(target, creds, self)
    elif not self.rules:
        # No rules to reference means we're going to fail closed
        result = False
    else:
        try:
            # Evaluate the rule
#通过打印rule中的方法,可以看到 "test:get_all" 获取到校验对象为 RuleCheck,直接打印只会打印一个字符串而已
            print type(self.rules[rule])   #<class 'oslo_policy._checks.RuleCheck'>
            #context_is_admin 打印出来的为RoleCheck
            print type(self.rules["context_is_admin"]) #<class 
            result = self.rules[rule](target, creds, self)
        except KeyError:
            LOG.debug('Rule [%s] does not exist', rule)
            # If the rule doesn't exist, fail closed
            result = False
    # If it is False, raise the exception if requested
    if do_raise and not result:
        if exc:
            raise exc(*args, **kwargs)
        raise PolicyNotAuthorized(rule, target, creds)
    return result

RuleCheck代码

代码位于/usr/lib/python2.7/site-packages/oslo_policy/_check.py

@register('rule')
class RuleCheck(Check):
    def __call__(self, target, creds, enforcer):
        try:
# 其最终的校验就是在这里,self.match 就是根据 rule:context_is_admin中拆出来的context_is_admin,在调用 RoleCheck的代码
            return enforcer.rules[self.match](target, creds, enforcer)
        except KeyError:
            # We don't have any matching rule; fail closed
            return False

RoleCheck代码

代码位于/usr/lib/python2.7/site-packages/oslo_policy/_check.py

@register('role') #装饰器的作用是将其注册到一个全局变量中
class RoleCheck(Check):
    """Check that there is a matching role in the ``creds`` dict."""

    def __call__(self, target, creds, enforcer):
        try:
            match = self.match % target
        except KeyError:
            # While doing RoleCheck if key not
            # present in Target return false
            return False
## 其最终的校验就是在这里,self.match 就是根据 role:admin中拆出来的admin,判断其是否在creds中的roles列表中
            # 具体的拆分,由下文的加载rule策略来说明

        if 'roles' in creds:
            return match.lower() in [x.lower() for x in creds['roles']]
        return False

装饰器:
def register(name, func=None):
    # Perform the actual decoration by registering the function or
    # class.  Returns the function or class for compliance with the
    # decorator interface.
    def decorator(func):
        registered_checks[name] = func
        return func

    # If the function or class is given, do the registration
    if func:
        return decorator(func)
    return decorator

所以registered_checks这个全局变量中保存着名称role对应的方法

rule的加载流程

代码位于/usr/lib/python2.7/site-packages/oslo_policy/policy.py中,通过load_rules走到这里

def _load_policy_file(self, path, force_reload, overwrite=True):
    reloaded, data = _cache_handler.read_cached_file(
        self._file_cache, path, force_reload=force_reload) #从policy.json配置文件中加载数据
    if reloaded or not self.rules:
        rules = Rules.load(data, self.default_rule) #根据policy对应rule,role的判断策略,加载对应的check方法
        self.set_rules(rules, overwrite=overwrite, use_conf=True)
        self._record_file_rules(data, overwrite)
        self._loaded_files.append(path)
        LOG.debug('Reloaded policy file: %(path)s', {'path': path})

Rules.load()

代码位于/usr/lib/python2.7/site-packages/oslo_policy/policy.py中

class Rules(dict):
    """A store for rules. Handles the default_rule setting directly."""
    @classmethod
    def load(cls, data, default_rule=None):
        """Allow loading of YAML/JSON rule data.
        .. versionadded:: 1.5.0
        """
        parsed_file = parse_file_contents(data)
        # Parse the rules
        # v ==> rule:admin_api,
        #       role:admin,
        #       is_admin:True or project_id:%(project_id)s,
        #       rule:admin_or_owner
        rules = {k: _parser.parse_rule(v) for k, v in parsed_file.items()}
        # parse_rule就是解析role:admin并对应上其RoleCheck的函数的方法

_parse.parse_rule

代码位于/usr/lib/python2.7/site-packages/oslo_policy/_parser.py中

def parse_rule(rule):
    """Parses a policy rule into a tree of :class:`.Check` objects."""

    # If the rule is a string, it's in the policy language
    if isinstance(rule, six.string_types):
        return _parse_text_rule(rule)
    return _parse_list_rule(rule)


def _parse_text_rule(rule):
    """Parses policy to the tree.

    Translates a policy written in the policy language into a tree of
    Check objects.
    """

    # Empty rule means always accept
    if not rule:
        return _checks.TrueCheck()

    # Parse the token stream
    state = ParseState()
    # role:admin
    for tok, value in _parse_tokenize(rule):
        state.shift(tok, value)
    try:
        return state.result  # 获取从 _parse_tokenize中读取到的唯一一个匹配规则
    except ValueError:
        # Couldn't parse the rule
        LOG.exception(_LE('Failed to understand rule %s'), rule)
        # Fail closed
        return _checks.FalseCheck()


def _parse_tokenize(rule):
    """Tokenizer for the policy language.

    Most of the single-character tokens are specified in the
    _tokenize_re; however, parentheses need to be handled specially,
    because they can appear inside a check string.  Thankfully, those
    parentheses that appear inside a check string can never occur at
    the very beginning or end ("%(variable)s" is the correct syntax).
    """

    for tok in _tokenize_re.split(rule): #根据空格拆分
        # Skip empty tokens
        if not tok or tok.isspace():
            continue

        # Handle leading parens on the token
        clean = tok.lstrip('(')
        for i in range(len(tok) - len(clean)):
            yield '(', '('

        # If it was only parentheses, continue
        if not clean:
            continue
        else:
            tok = clean

        # Handle trailing parens on the token
        clean = tok.rstrip(')')
        trail = len(tok) - len(clean)

        # Yield the cleaned token
        lowered = clean.lower()
        if lowered in ('and', 'or', 'not'):
            # Special tokens
            yield lowered, clean
        elif clean:
            # Not a special token, but not composed solely of ')'
            if len(tok) >= 2 and ((tok[0], tok[-1]) in
                                  [('"', '"'), ("'", "'")]):
                # It's a quoted string
                yield 'string', tok[1:-1]
            else:
                print "clean is :%s." % clean
                yield 'check', _parse_check(clean) 
#通过以上的拆分,clean为role:admin

        # Yield the trailing parens
        for i in range(trail):
            yield ')', ')'

_parse_check

def _parse_check(rule):
    """Parse a single base check rule into an appropriate Check object."""

    # Handle the special checks
    if rule == '!':
        return _checks.FalseCheck()
    elif rule == '@':
        return _checks.TrueCheck()

    try:
        kind, match = rule.split(':', 1) #假设rule为role:admin,
    except Exception:
        LOG.exception(_LE('Failed to understand rule %s'), rule)
        # If the rule is invalid, we'll fail closed
        return _checks.FalseCheck()

    # Find what implements the check
    if kind in _checks.registered_checks: #从rule的全局变量中获取匹配的方法
        return _checks.registered_checks[kind](kind, match)#实例化rule的参数,加载完成
    elif None in _checks.registered_checks:
        return _checks.registered_checks[None](kind, match)
    else:
        LOG.error(_LE('No handler for matches of kind %s'), kind)
        return _checks.FalseCheck()


分析以上全部完成。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值