openstack policy 鑒權過程分析
轉:http://blog.chinaunix.net/uid-20940095-id-4144300.html
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等信息,具體如下:
(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,稍後會給出一個自己實現的例子。
openstack policy 鑒權過程分析