1. 程式人生 > >openstack policy 鑒權過程分析

openstack policy 鑒權過程分析

ext 對應關系 重啟 ech author repr glob this service

轉: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 were going to fail closed
        result = False
    else:
        try:
            # Evaluate the rule
            result = _rules[rule](target, creds)  ##沒一條rule執行一個函數,這個對應關系記錄在全局變量_rules
        except KeyError:
            # If the rule doesnt 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 鑒權過程分析