在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()
分析以上全部完成。