问题导读
1、你如何理解openstack认证过程?
2、怎样分析policy.json源代码文件?
3、如何添加自定义的rule?
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'http://10.2.45.133:8776/v1/999c9fb0d7684ce1913cac4cc6122e51', u'region': u'RegionOne', u'id': u'0987e932f0a0408ca7a5a31200c8ac51', u'internalURL': u'http://10.2.45.133:8776/v1/999c9fb0d7684ce1913cac4cc6122e51', u'publicURL': u'http://10.2.45.133:8776/v1/999c9fb0d7684ce1913cac4cc6122e51'}], 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,稍后会给出一个自己实现的例子。
|