1. openstack 鉴权简单介绍
众所周知,openstack通过keystone用来完成authenticate(认证),真正的鉴权(authorize)是在各个模块分别做的,具体实现为每个模块都有一个policy文件,叫policy.json,里面定义了鉴权用的rules。
以nova为例,policy文件的位置在:/etc/nova/policy.json,下面先来看几条rules,了解其基本含义:
-
"compute:create": "",
-
"compute:create:attach_network": "",
-
"compute:create:attach_volume": "",
-
"compute:create:forced_host": "is_admin:True",
-
"compute:get_all": "",
-
"compute:get_all_tenants": "",
-
"compute:start": "rule:admin_or_owner",
-
"compute:stop": "rule:admin_or_owner",
-
"compute:unlock_override": "rule:admin_api",
语法规则为:rule:[result]
rule:指这条规则是干啥的,通常对应一个action,以类似scope:action的形式给出,scope表示作用范围,action表示执行哪种操作
result: 表示这条rule的判定结果或者如何进行判定,比如
"compute:create:forced_host": "is_admin:True",如果执行此操作的用户具有admin角色(role),则这条结果的判定结果就是True。
另外,rule是可以嵌套的,比如
"compute:stop": "rule:admin_or_owner",表示compute:stop这条规则的结果为
admin_or_owner这条规则的结果,而
admin_or_owner规则如下:
-
"admin_or_owner": "is_admin:True or project_id:%(project_id)s",
如果调用这个操作的用户的角色是admin,就返回True,或者返回用户所属的project的id.
2. policy鉴权代码分析
针对每一个操作,都会经过一个叫@wrap_check_policy的decorator,以nova的resize操作为例,在执行真正的resize代码之前,先要经过一个叫
@wrap_check_policy的装饰器来完成policy的check过程,具体参见后面的代码check_policy函数:
-
@wrap_check_policy
-
@check_instance_lock
-
@check_instance_cell
-
@check_instance_state(vm_state=[vm_states.ACTIVE, vm_states.STOPPED],
-
task_state=[None])
-
def resize(self, context, instance, flavor_id=None,
-
**extra_instance_updates):
check_policy(context, action, target, scope='compute')函数有四个参数:
(1) context: 执行resize操作的上下文,其内容包括project_id, user_id, role,auth_token等信息,具体如下:
-
{'project_name': u'demo', 'user_id': u'a51e07e52af24111973dd7e11ece97f3', 'roles': [u'admin'], 'timestamp': '2014-03-10T08:45:56.552624', 'auth_token': '851012cfd5ad220e02cc3bc61b31c5f5', 'remote_address': '10.2.45.133', 'quota_class': None, 'is_admin': True, 'tenant': u'999c9fb0d7684ce1913cac4cc6122e51', 'service_catalog': [{u'endpoints': [{u'adminURL': u'', u'region': u'RegionOne', u'id': u'0987e932f0a0408ca7a5a31200c8ac51', u'internalURL': u'', u'publicURL': u''}], u'endpoints_links': [], u'type': u'volume', u'name': u'cinder'}], 'request_id': 'req-292b93ac-0a2b-488e-8a51-ea734286b07c', 'instance_lock_checked': False, 'project_id': u'999c9fb0d7684ce1913cac4cc6122e51', 'user_name': u'admin', 'read_deleted': 'no', 'user': u'a51e07e52af24111973dd7e11ece97f3'}
(2) action:表示当前执行的操作是啥,这里就是resize
(3) target:操作针对的object是啥,这里就是instance id
(4) scope:当前操作的作用域是啥,主要为了与policy文件中定义的作用域匹配,这里为compute,即nova执行的操作
-
def check_policy(context, action, target, scope='compute'):
-
_action = '%s:%s' % (scope, action) ##这里拼接成policy.json的rule,即_action=compute:resize
-
nova.policy.enforce(context, _action, target)
-
------------------------------------------------------------------------------------------------------------------------
-
def enforce(context, action, target, do_raise=True):
-
"""Verifies that the action is valid on the target in this context.
-
-
:param context: nova context
-
:param action: string representing the action to be checked
-
this should be colon separated for clarity.
-
i.e. ``compute:create_instance``,
-
``compute:attach_volume``,
-
``volume:attach_volume``
-
:param target: dictionary representing the object of the action
-
for object creation this should be a dictionary representing the
-
location of the object e.g. ``{'project_id': context.project_id}``
-
:param do_raise: if True (the default), raises PolicyNotAuthorized;
-
if False, returns False
-
-
:raises nova.exception.PolicyNotAuthorized: if verification fails
-
and do_raise is True.
-
-
:return: returns a non-False value (not necessarily "True") if
-
authorized, and the exact value False if not authorized and
-
do_raise is False.
-
"""
-
init() ##policy.json被cache到cache_info数据结构中,init()函数就是去检查policy.json是否已经被加载或修改过,如果cache_info结构为空,说明policy.json还没有加载过,则执行加载;如果policy.json被修改过,也会重新进行加载
-
-
credentials = context.to_dict() ##将context转化成dictonary,就是上面context给出的内容,以便后面代码使用
-
-
# Add the exception arguments if asked to do a raise
-
extra = {}
-
if do_raise:
-
extra.update(exc=exception.PolicyNotAuthorized, action=action) ##增加no auth hook函数,即如果rule的结果为False,则执行no auth hook函数做一些处理
-
-
return policy.check(action, target, credentials, **extra) ##进行policy的check
-
--------------------------------------------------------------------------------------------------------------------
-
def init():
-
global _POLICY_PATH
-
global _POLICY_CACHE
-
if not _POLICY_PATH:
-
_POLICY_PATH = CONF.policy_file
-
if not os.path.exists(_POLICY_PATH):
-
_POLICY_PATH = CONF.find_file(_POLICY_PATH)
-
if not _POLICY_PATH:
-
raise exception.ConfigNotFound(path=CONF.policy_file)
-
utils.read_cached_file(_POLICY_PATH, _POLICY_CACHE,
-
reload_func=_set_rules) ##加载policy.json文件
-
----------------------------------------------------------------------------------------------------------------------
-
-
def read_cached_file(filename, cache_info, reload_func=None):
-
"""Read from a file if it has been modified.
-
-
:param cache_info: dictionary to hold opaque cache.
-
:param reload_func: optional function to be called with data when
-
file is reloaded due to a modification.
-
-
:returns: data from file
-
-
"""
-
mtime = os.path.getmtime(filename) ###获取policy.json文件的modify time,如果与cache_info中的mtime不同,则说明文件被修改过,则执行重新加载
-
if not cache_info or mtime != cache_info.get('mtime'):
-
LOG.debug(_("Reloading cached file %s") % filename)
-
with open(filename) as fap:
-
cache_info['data'] = fap.read()
-
cache_info['mtime'] = mtime
-
if reload_func:
-
reload_func(cache_info['data'])
-
return cache_info['data'] ###返回加载后的policy.json文件的内容
-
---------------------------------------------------------------------------------------------------------------------------
-
def check(rule, target, creds, exc=None, *args, **kwargs):
-
"""
-
Checks authorization of a rule against the target and credentials.
-
-
:param rule: The rule to evaluate.
-
:param target: As much information about the object being operated
-
on as possible, as a dictionary.
-
:param creds: As much information about the user performing the
-
action as possible, as a dictionary.
-
:param exc: Class of the exception to raise if the check fails.
-
Any remaining arguments passed to check() (both
-
positional and keyword arguments) will be passed to
-
the exception class. If exc is not provided, returns
-
False.
-
-
:return: Returns 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.
-
"""
-
-
# Allow the rule to be a Check tree
-
if isinstance(rule, BaseCheck):
-
result = rule(target, creds)
-
elif not _rules:
-
# No rules to reference means we're going to fail closed
-
result = False
-
else:
-
try:
-
# Evaluate the rule
-
result = _rules[rule](target, creds) ##没一条rule执行一个函数,这个对应关系记录在全局变量_rules
-
except KeyError:
-
# If the rule doesn't exist, fail closed
-
result = False
-
-
# If it is False, raise the exception if requested
-
if exc and result is False:
-
raise exc(*args, **kwargs)
-
-
return result
3. 总结
之前一直以为修改了policy.json文件,需要重启service才能重新加载policy.json生效,通过分析代码,证明policy.json是动态更新的。另外,通过分析代码,也搞清楚了如何添加自定义的rule,以便实现更细粒度的rule,稍后会给出一个自己实现的例子。
微博: @Marshal-Liu
Email: ustcdylan@gmail.com