openstack各組件的RBAC(Role Based Access Controll)

以kilo版本的openstack為例

Keystone

Role 和 Policy 策略

驗證用戶username和password以后,用戶有沒有權(quán)限來執(zhí)行某個操作則取決于 Role 和 基于 Roles 的鑒權(quán)。

Keystone API V3 與 V2 相比,對 policy 的支持有很多的增強(qiáng)。V2 的支持和 OpenStack 其它組件比如cinder,nova 等差不多,只支持基于 policy.json 文件的 policy 控制;而 V3 中,支持對 policy 的 CURD 操作,以及對 endpoint,service 等的 policy 操作等。

這里主要介紹一下V2版本的RBAC

Keystone 在每個子模塊的 controller.py 文件中的各個控制類的方法上實施 RBAC。有三種方法

1.在函數(shù)體中使用 self.assert_admin(context)
2.使用裝飾器@controller.protected()
3.使用裝飾器@controller.filterprotected('type', 'name')

來看一下他們各自的實現(xiàn)

assert_admin

keystone.common.wsgi.Application.assert_admin:
    # 如果context中is_admin是True, 直接跳過下面的驗證
    if not context['is_admin']:
        try:
            user_token_ref = token_model.KeystoneToken(
                token_id=context['token_id'],
                token_data=self.token_provider_api.validate_token(
                    context['token_id']))
        except exception.TokenNotFound as e:
            raise exception.Unauthorized(e)

        validate_token_bind(context, user_token_ref)
        creds = copy.deepcopy(user_token_ref.metadata)

        try:
            creds['user_id'] = user_token_ref.user_id
        except exception.UnexpectedError:
            LOG.debug('Invalid user')
            raise exception.Unauthorized()

        if user_token_ref.project_scoped:
            creds['tenant_id'] = user_token_ref.project_id
        else:
            LOG.debug('Invalid tenant')
            raise exception.Unauthorized()

        creds['roles'] = user_token_ref.role_names
        # Accept either is_admin or the admin role
        self.policy_api.enforce(creds, 'admin_required', {})
    ↓
keystone.policy.backends.rules.Policy.enforce:
    enforce(credentials, action, target)
    ↓
keystone.policy.backends.rules.enforce:
    return _ENFORCER.enforce(action, target, credentials, **extra)
    ↓
oslo_policy.policy.Enforcer.enforce(self, rule, target, creds, do_raise=False,
                exc=None, *args, **kwargs):
    # 這里的rule為admin_required,target為{},creds為包含user_id,tenant_id,tenant_id的字典
    self.load_rules()
    
    # Allow the rule to be a Check tree
    # 這庫的
    if isinstance(rule, _checks.BaseCheck):
        result = rule(target, creds, self)
    elif not self.rules:
        # No rules to reference means we're going to fail closed
        result = False
    else:
        try:
            # Evaluate the rule
            result = self.rules[rule](target, creds, self)
        except KeyError:
            LOG.debug('Rule [%s] does not exist' % rule)
            # If the rule doesn't exist, fail closed
            result = False

    # If it is False, raise the exception if requested
    if do_raise and not result:
        if exc:
            raise exc(*args, **kwargs)

        raise PolicyNotAuthorized(rule, target, creds)

    return results   

下面這行代碼比較關(guān)鍵,rule的值為'admin_required,根據(jù)/etc/keystone/policy.json文件讀出來的值為(role:admin or is_admin:1),所以selef.rules[rule]的type是
<class 'oslo_policy._checks.OrCheck'>

result = self.rules[rule](target, creds, self)

再來看看OrCheck的代碼:

class OrCheck(BaseCheck):
    def __init__(self, rules):
        self.rules = rules
        
    def __call__(self, target, cred, enforcer):
        """Check the policy.

        Requires that at least one rule accept in order to return True.
        """

        for rule in self.rules:
            if rule(target, cred, enforcer):
                return True
        return False

@controller.protected()

keystone.common.controller.protected:
    def wrapper(f):
        @functools.wraps(f)
        def inner(self, context, *args, **kwargs):
            if 'is_admin' in context and context['is_admin']:
                LOG.warning(_LW('RBAC: Bypassing authorization'))
            elif callback is not None:
                prep_info = {'f_name': f.__name__,
                             'input_attr': kwargs}
                callback(self, context, prep_info, *args, **kwargs)
            else:
                action = 'identity:%s' % f.__name__
                creds = _build_policy_check_credentials(self, action,
                                                        context, kwargs)

                policy_dict = {}
                if (hasattr(self, 'get_member_from_driver') and
                        self.get_member_from_driver is not None):
                    key = '%s_id' % self.member_name
                    if key in kwargs:
                        ref = self.get_member_from_driver(kwargs[key])
                        policy_dict['target'] = {self.member_name: ref}

                
                if context.get('subject_token_id') is not None:
                     token_ref = token_model.KeystoneToken(
                        token_id=context['subject_token_id'],
                        token_data=self.token_provider_api.validate_token(
                            context['subject_token_id']))
                    policy_dict.setdefault('target', {})
                    policy_dict['target'].setdefault(self.member_name, {})
                    policy_dict['target'][self.member_name]['user_id'] = (
                        token_ref.user_id)
                    try:
                        user_domain_id = token_ref.user_domain_id
                    except exception.UnexpectedError:
                        user_domain_id = None
                    if user_domain_id:
                        policy_dict['target'][self.member_name].setdefault(
                            'user', {})
                        policy_dict['target'][self.member_name][
                            'user'].setdefault('domain', {})
                        policy_dict['target'][self.member_name]['user'][
                            'domain']['id'] = (
                                user_domain_id)

                policy_dict.update(kwargs)
                self.policy_api.enforce(creds,
                                        action,
                                        utils.flatten_dict(policy_dict))
                LOG.debug('RBAC: Authorization granted')
            return f(self, context, *args, **kwargs)
        return inner
    return wrapper

通過這種方法最終也是調(diào)用了self.policy_api.enforce來鑒權(quán),不過注意到其中的這行代碼:

action = 'identity:%s' % f.__name__

所以,在控制類的方法上加上此裝飾器,如:

@controller.protected()
    def create_domain(self, context, domain):

對面的action即為identity:create_domain

@controller.filterprotected('type', 'name')

@controller.filterprotected('enabled', 'name')
    def list_domains(self, context, filters):

具體的實現(xiàn)代碼和protected類似。

nova

以創(chuàng)建虛擬機(jī)為例

nova.compute.api.API.create:
    self._check_create_policies(context, availability_zone,
                                requested_networks, block_device_mapping)
    ↓
nova.compute.api.API._check_create_policies:
        """Check policies for create()."""
        target = {'project_id': context.project_id,
                  'user_id': context.user_id,
                  'availability_zone': availability_zone}
        if not self.skip_policy_check:
            # 檢查創(chuàng)建虛擬機(jī)的權(quán)限
            check_policy(context, 'create', target)

            if requested_networks and len(requested_networks):
                check_policy(context, 'create:attach_network', target)

            if block_device_mapping:
                check_policy(context, 'create:attach_volume', target)
    ↓
def check_policy(context, action, target, scope='compute'):
    _action = '%s:%s' % (scope, action)
    nova.policy.enforce(context, _action, target)
    ↓
nova.policy.enforce:
    init()
    credentials = context.to_dict()
    if not exc:
        exc = exception.PolicyNotAuthorized
    try:
        result = _ENFORCER.enforce(action, target, credentials,
                                   do_raise=do_raise, exc=exc, action=action)
    except Exception:
        credentials.pop('auth_token', None)
        with excutils.save_and_reraise_exception():
            LOG.debug('Policy check for %(action)s failed with credentials '
                      '%(credentials)s',
                      {'action': action, 'credentials': credentials})
    return result
    ↓
nova.openstack.common.policy.Enforcer.enforce:
    self.load_rules() #重新讀取 policy.json 文件
    ...
    result = self.rules[rule](target, creds, self) #使用特定的 rule 來檢查 user 的權(quán)限
    ...
    return True or PolicyNotAuthorized(rule)

cinder

以創(chuàng)建cinder volume為例

cinder.volume.flows.api.create_volume.ExtractVolumeRequestTask.execute:
    # ACTION = 'volume:create'
    policy.enforce_action(context, ACTION)
    ↓
cinder.policy.enforce_action:
    return enforce(context, action, {'project_id': context.project_id,
                                     'user_id': context.user_id})
    ↓
cinder.policy.enforce:
    init()

    return _ENFORCER.enforce(action, target, context.to_dict(),
                             do_raise=True,
                             exc=exception.PolicyNotAuthorized,
                             action=action)
    ↓
cinder.openstack.common.policy.Enforcer.enforce:
    #rule:volume:create
    #target: {'project_id': u'xx', 'user_id': u'xx'},  
    #creds 中包括 user role 等信息
    self.load_rules() #重新讀取 policy.json 文件
    ...
    result = self.rules[rule](target, creds, self) #使用特定的 rule 來檢查 user 的權(quán)限
    ...
    return True or PolicyNotAuthorized(rule)

參考

世民談云計算

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內(nèi)容