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

openstack policy 鑑權過程分析

  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,稍後會給出一個自己實現的例子。