OpenStack公共組件oslo之十二——oslo.policy 2017-12-26

? ? ? ? 眾所周知,OpenStack使用基于角色的權(quán)限訪問控制(RBAC),在RBAC中,權(quán)限與角色相關(guān)聯(lián),用戶通過成為適當(dāng)角色的成員而得到這些角色的權(quán)限。這就極大地簡化了權(quán)限的管理。在一個(gè)組織中,角色是為了完成各種工作而創(chuàng)造,用戶則依據(jù)它的責(zé)任和資格來被指派相應(yīng)的角色,用戶可以很容易地從一個(gè)角色被指派到另一個(gè)角色。角色可依新的需求和系統(tǒng)的合并而賦予新的權(quán)限,而權(quán)限也可根據(jù)需要而從某角色中回收。角色與角色的關(guān)系可以建立起來以囊括更廣泛的客觀情況。為了更好的適應(yīng)OpenStack的角色權(quán)限管理,oslo項(xiàng)目創(chuàng)建了oslo.policy子項(xiàng)目為所有OpenStack服務(wù)提供RBAC策略實(shí)施支持。本文將詳細(xì)介紹oslo.policy的實(shí)現(xiàn)與使用。

1 策略規(guī)則表達(dá)式

在通用的策略引擎實(shí)現(xiàn)中,策略規(guī)則表達(dá)式一般包含一個(gè)目標(biāo)和一個(gè)相關(guān)聯(lián)的規(guī)則。具體示例如下:

"<target>": <rule> ?

其中,target指定了正在執(zhí)行策略的服務(wù);通常,一個(gè)target指的是一個(gè)API調(diào)用。rule代表了具體的策略規(guī)則,一般可以用以下兩種形式表示:一個(gè)使用新策略語法寫成的字符串或者一個(gè)策略規(guī)則的列表。OpenStack推薦使用字符串格式,因?yàn)樗菀桌斫狻?/p>

在策略語法中,每個(gè)規(guī)則檢查都被指定為一個(gè)簡單的"a:b"對形式,該"a:b"對通過匹配與之對應(yīng)的類來執(zhí)行檢查訪問權(quán)限。這些"a:b"對的類型與格式可以歸納為表1所示。

表1 策略語法類型與格式

類型格式

用戶的角色role:admin

policy中定義的規(guī)則rule:admin_required

拒絕訪問的URL(URL檢查返回True才有效)http://my-url.org/check

用戶屬性(可通過token獲得,包括user_id、domain_id和project_id等)project_id:%(target.project.id)s

字符串:’xpto2035abc’

‘myproject’:

字面量project_id:xpto2035abc

domain_id:20

True:%(user.enabled)s

在字符串格式的策略規(guī)則表達(dá)式中,如果需要使用多個(gè)規(guī)則,可以使用連接運(yùn)算符and或or,and表示與,or表示或。下面的例子表示允許角色為admin的用戶或項(xiàng)目ID為%(project_id)s且角色為projectadmin的用戶訪問。

"role:admin?or?(project_id:%(project_id)s?and?role:projectadmin)"

另外,策略規(guī)則表達(dá)式中還可以使用not運(yùn)算符表取反。下面的例子表示允許項(xiàng)目ID為%(project_id)s且角色不是dunce的用戶訪問。

"project_id:%(project_id)s?and?not?role:dunce"

在策略規(guī)則表達(dá)式中,各運(yùn)算符的優(yōu)先級(jí)如表2所示。其中,數(shù)字越大代表優(yōu)先級(jí)越高。

表2 策略規(guī)則表達(dá)式運(yùn)算符優(yōu)先級(jí)

優(yōu)先級(jí)類型表達(dá)式

4組運(yùn)算(...)

3邏輯否運(yùn)算not ...

2邏輯與運(yùn)算... and ...

1邏輯或運(yùn)算... or ...

在列表形式的策略規(guī)則表達(dá)式中,使用"[]"表示邏輯與運(yùn)算,在"[]"內(nèi)的多個(gè)規(guī)則使用","連接,在進(jìn)行權(quán)限檢查時(shí),只有"[]"中的所有規(guī)則都滿足才可以通過檢查;而同一級(jí)的不同"[]"表示邏輯或運(yùn)算,用","連接,表示只要滿足其中一個(gè)"[]"定義的規(guī)則即可訪問;另外,使用"@"表示始終允許訪問,使用"!"表示拒絕訪問。基于此,上述示例表達(dá)式使用列表形式可以表示如下:

[["role:admin"],?["project_id:%(project_id)s","role:projectadmin"]]

需要注意的是,如果一個(gè)規(guī)則定義為一個(gè)空列表[]或一個(gè)空字符串"",表示該target始終允許訪問。

2 規(guī)則檢查

在oslo.policy中,所有規(guī)則的封裝與檢查操作都定義在oslo_policy._checks模塊中,該模塊首先定義了一個(gè)BaseCheck抽象類,所有對規(guī)則檢查的封裝都需要繼承該抽象類。在繼承BaseCheck類時(shí),每個(gè)規(guī)則檢查類都需要覆寫__str__()方法,用于顯示該檢查的含義;還需要覆寫__call__()方法,在執(zhí)行具體的檢查操作時(shí)通過調(diào)用具體規(guī)則檢查類的__call__()方法實(shí)現(xiàn)。接下來,本文首先介紹oslo.policy中定義了規(guī)則檢查類。而針對"a:b"格式的策略規(guī)則表達(dá)式,oslo.policy定義了一個(gè)繼承BaseCheck類的Check類來表示,并為其定義了kind屬性表示該條規(guī)則檢查的類型,即a,match屬性表示具體匹配的值,即b。

2.1?GenericCheck

GenericCheck類通常用于匹配與API調(diào)用一起發(fā)送的屬性。通過以下語法,策略引擎可以使用這些屬性(位于策略規(guī)則表達(dá)式的右側(cè)):

user_id:%(user.id)s

在上述語法中,右側(cè)的值是一個(gè)字符串或者使用規(guī)定的Python字符串替換??捎玫膶傩院椭等Q于使用公共策略引擎的程序。所有這些屬性(與用戶、API調(diào)用和上下文相關(guān)的)都可以相互檢查,或?qū)潭ǖ某A窟M(jìn)行檢查。

GenericCheck類可以對通過令牌獲取的以下幾種用戶屬性執(zhí)行策略檢查:

user_id

domain_id和project_id(取決于token指定的范圍)

給定的token范圍內(nèi)的角色role列表

2.2 特殊檢查類

特殊檢查類是相較于GenericCheck類而言的,特殊檢查類可以提供更加靈活的檢查機(jī)制。oslo.policy內(nèi)置的特殊檢查類包括RoleCheck、RuleCheck、HTTPCheck等。

RoleCheck類:該類用于檢查提供的憑證中是否存在指定的角色。一個(gè)角色檢查的表達(dá)式如下:

"role:<role_name>" ?

RuleCheck類:該類用于通過名稱引用另一個(gè)已定義的規(guī)則。這樣,一個(gè)普通的規(guī)則可以定義為可重用的規(guī)則,然后在其他規(guī)則中引用該規(guī)則。它還適用于將一組檢查定義為一個(gè)更具描述性的命名的情況,這樣便于策略的可讀性。一個(gè)規(guī)則檢查的表達(dá)式如下,在這個(gè)示例中,將定義好的admin_required規(guī)則進(jìn)行了重用。

"admin_required":"role:admin"

"<target>": "rule:admin_required" ?

HTTPCheck類:該類用于向遠(yuǎn)程服務(wù)器發(fā)送HTTP請求,以確定檢查結(jié)果。target和憑證將傳遞到遠(yuǎn)程服務(wù)器進(jìn)行檢查,如果遠(yuǎn)程服務(wù)器返回的響應(yīng)結(jié)果為True,則表示該操作通過權(quán)限驗(yàn)證。HTTP檢查的表達(dá)式如下,預(yù)期目標(biāo)URL包含字符串格式的關(guān)鍵字,這個(gè)關(guān)鍵字是目標(biāo)字典的key鍵。

"http:<target URI>" ?

"http://server.test/%(name)s"?

2.3 運(yùn)算符檢查類

oslo.policy為第1節(jié)中提到的各種運(yùn)算符也提供了相應(yīng)的檢查類,主要包含以下幾種檢查類:

FalseCheck類:相當(dāng)于運(yùn)算符"!",即總是返回False,表示始終不允許訪問。

TrueCheck類:相當(dāng)于運(yùn)算符"@",即總是返回True,表示始終允許訪問。

NotCheck類:相當(dāng)于取反運(yùn)算符"not ..."。

AndCheck類:相當(dāng)于邏輯與運(yùn)算符"... and ..."。

OrCheck類:相當(dāng)于邏輯或運(yùn)算符"... or ..."。

除了上述的三類檢查類,用戶也可以自定義檢查類。在自定義檢查類時(shí),首先需要繼承BaseCheck類或Check類,然后覆寫__str__()和__call__()方法:__str__()方法用于返回以此節(jié)點(diǎn)為根的Check樹的字符串表示形式,用于打?。籣_call__()方法實(shí)現(xiàn)了具體的匹配算法。另外,還需要注意的是自定義檢查類時(shí)還應(yīng)該使用oslo_policy._checks模塊下的register(name, func=None)裝飾器將其緩存在檢查類字典中,便于查找。

3 oslo.policy的權(quán)限檢查實(shí)現(xiàn)原理

oslo.policy中權(quán)限檢查的實(shí)現(xiàn)主要定義在oslo_policy.policy模塊下。該模塊中定義了用于保存規(guī)則,加載和檢查規(guī)則以及定義策略的多個(gè)類。這些類的實(shí)現(xiàn)如下:

Rules類:該類用于緩存所有的規(guī)則,其可以直接處理default_rule的設(shè)置。該類提供了load(data, default_rule=None)和load(data, default_rule=None)可以從策略的YAML或JSON配置文件中加載所有規(guī)則;還提供了from_dict(rules_dict, default_rule=None)一個(gè)指定的字典中加載所有規(guī)則。

Enforcer類:該類負(fù)責(zé)加載和執(zhí)行規(guī)則。該類提供了load_rules(force_reload=False)從該類的實(shí)例化對象綁定的策略文件路徑加載所有規(guī)則,如果force_reload為True,表示從配置文件重新加載數(shù)據(jù);提供了register_default(default)注冊一個(gè)默認(rèn)的RuleDefault對象,提供了register_defaults(defaults)注冊一組RuleDefault對象;提供了enforce(rule, target, creds, do_raise=False, exc=None, *args, **kwargs)根據(jù)目標(biāo)target和憑證檢查規(guī)則的權(quán)限,該方法通常需要結(jié)合authorize(self, rule, target, creds, do_raise=False, exc=None, *args, **kwargs)裝飾器使用,以保證待檢查的策略規(guī)則已經(jīng)被注冊;該類還提供了check_rules(raise_on_violation=False)檢查是否存在明顯不正確的規(guī)則,如未定義的規(guī)則或循環(huán)引用的規(guī)則等。

RuleDefault類:該類用于定義一個(gè)權(quán)限檢查策略,創(chuàng)建時(shí)需要指定名稱name和值check_str,建議對該策略定義一個(gè)詳細(xì)的說明description。

DocumentedRuleDefault類:該類用于定義一個(gè)policy-in-code的策略對象。該類的功能與RuleDefault類似,但它還需要一些與注冊的策略規(guī)則有關(guān)的額外的數(shù)據(jù)。這樣就可以根據(jù)這個(gè)類的屬性來呈現(xiàn)與之對應(yīng)的文檔。最終,oslo.policy都會(huì)使用該類而棄用RuleDefault。創(chuàng)建該類的對象時(shí),必須指定名稱name、值check_str、描述信息description;另外,還需要定義一個(gè)operations屬性,這個(gè)屬性包含了每個(gè)API的URL和相應(yīng)HTTP請求方法的字典。下面是一個(gè)operations屬性的示例。

operations=[{'path': '/foo', 'method': 'GET'}, ?

? ? ? ? ? ? {'path': '/some', 'method': 'POST'}]?

DeprecatedRule類:該類主要用于表示一個(gè)被廢棄的策略或規(guī)則。

4 oslo.policy的使用方法

本節(jié)結(jié)合1-3節(jié)的內(nèi)容以及nova組件介紹oslo.policy的使用方法。一般地,OpenStack其他組件可以直接使用oslo.policy庫實(shí)現(xiàn)RBAC策略,也可以對oslo.policy庫進(jìn)行擴(kuò)展,實(shí)現(xiàn)適合自身使用的RBAC策略。nova組件就是對oslo.policy的實(shí)現(xiàn)進(jìn)行了擴(kuò)展。

首先為了實(shí)現(xiàn)nova自身的需求,nova組件實(shí)現(xiàn)了一個(gè)檢查類。

@policy.register('is_admin') ?

class IsAdminCheck(policy.Check): ?

? ? """An explicit check for is_admin.""" ?


? ? def __init__(self, kind, match): ?

? ? ? ? """Initialize the check.""" ?


? ? ? ? self.expected = (match.lower() == 'true') ?


? ? ? ? super(IsAdminCheck, self).__init__(kind, str(self.expected)) ?


? ? def __call__(self, target, creds, enforcer): ?

? ? ? ? """Determine whether is_admin matches the requested value.""" ?


? ? ? ? return creds['is_admin'] == self.expected ?

這個(gè)類用于判斷用戶是否是admin用戶。接著,nova組件實(shí)現(xiàn)了一個(gè)初始化方法init()。

fromoslo_configimportcfg

fromoslo_logimportlog?as?logging

fromoslo_policyimportpolicy

definit(policy_file=None,?rules=None,?default_rule=None,?use_conf=True):

"""Init?an?Enforcer?class.

:param?policy_file:?Custom?policy?file?to?use,?if?none?is?specified,

`CONF.policy_file`?will?be?used.

:param?rules:?Default?dictionary?/?Rules?to?use.?It?will?be

considered?just?in?the?first?instantiation.

:param?default_rule:?Default?rule?to?use,?CONF.default_rule?will

be?used?if?none?is?specified.

:param?use_conf:?Whether?to?load?rules?from?config?file.

"""

global_ENFORCER

globalsaved_file_rules

ifnot_ENFORCER:

_ENFORCER?=?policy.Enforcer(CONF,

policy_file=policy_file,

rules=rules,

default_rule=default_rule,

use_conf=use_conf)

register_rules(_ENFORCER)

_ENFORCER.load_rules()

#?Only?the?rules?which?are?loaded?from?file?may?be?changed.

current_file_rules?=?_ENFORCER.file_rules

current_file_rules?=?_serialize_rules(current_file_rules)

#?Checks?whether?the?rules?are?updated?in?the?runtime

ifsaved_file_rules?!=?current_file_rules:

_warning_for_deprecated_user_based_rules(current_file_rules)

saved_file_rules?=?copy.deepcopy(current_file_rules)

該初始化方法創(chuàng)建了一個(gè)Enforcer對象,并將所有策略規(guī)則都加載到緩存中備用。然后,nova組件分別定義了兩個(gè)用于檢查規(guī)則的方法。

[python]view plaincopy

defauthorize(context,?action,?target,?do_raise=True,?exc=None):

"""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

:param?exc:?Class?of?the?exception?to?raise?if?the?check?fails.

Any?remaining?arguments?passed?to?:meth:`authorize`?(both

positional?and?keyword?arguments)?will?be?passed?to

the?exception?class.?If?not?specified,

:class:`PolicyNotAuthorized`?will?be?used.

:raises?nova.exception.PolicyNotAuthorized:?if?verification?fails

and?do_raise?is?True.?Or?if?'exc'?is?specified?it?will?raise?an

exception?of?that?type.

: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()

credentials?=?context.to_policy_values()

ifnotexc:

exc?=?exception.PolicyNotAuthorized

try:

result?=?_ENFORCER.authorize(action,?target,?credentials,

do_raise=do_raise,?exc=exc,?action=action)

exceptpolicy.PolicyNotRegistered:

with?excutils.save_and_reraise_exception():

LOG.exception(_LE('Policy?not?registered'))

exceptException:

with?excutils.save_and_reraise_exception():

LOG.debug('Policy?check?for?%(action)s?failed?with?credentials?'

'%(credentials)s',

{'action':?action,'credentials':?credentials})

returnresult

defcheck_is_admin(context):

"""Whether?or?not?roles?contains?'admin'?role?according?to?policy?setting.

"""

init()

#?the?target?is?user-self

credentials?=?context.to_policy_values()

target?=?credentials

return_ENFORCER.authorize('context_is_admin',?target,?credentials)

其中,authorize()方法對oslo.policy中的所有默認(rèn)規(guī)則進(jìn)行檢查;而check_is_admin()方法只對nova自定義的IsAdminCheck類的規(guī)則進(jìn)行檢查。緊接著,nova組件定義了一個(gè)用戶獲取Enforcer對象的方法。

defget_enforcer():

#?This?method?is?for?use?by?oslopolicy?CLI?scripts.?Those?scripts?need?the

#?'output-file'?and?'namespace'?options,?but?having?those?in?sys.argv?means

#?loading?the?Nova?config?options?will?fail?as?those?are?not?expected?to

#?be?present.?So?we?pass?in?an?arg?list?with?those?stripped?out.

conf_args?=?[]

#?Start?at?1?because?cfg.CONF?expects?the?equivalent?of?sys.argv[1:]

i?=1

whilei?<?len(sys.argv):

ifsys.argv[i].strip('-')in['namespace','output-file']:

i?+=2

continue

conf_args.append(sys.argv[i])

i?+=1

cfg.CONF(conf_args,?project='nova')

init()

return_ENFORCER

該方法通過讀取配置文件或輸入?yún)?shù)中的相關(guān)配置信息,獲取一個(gè)Enforcer對象。因此,為了實(shí)現(xiàn)oslo.policy的功能,需要在nova組件的配置文件中添加如下的配置信息:

[python]view plaincopy

[DEFAULT]

output_file?=?policy-sample.yaml

namespace?=?nova

其中,namespace為oslo.policy添加了一個(gè)命名空間,這樣可以隔離OpenStack的不同組件;output_file為默認(rèn)策略規(guī)則文件的輸出路徑,當(dāng)然你也可以自定義一個(gè)策略規(guī)則文件。

如果需要為定義的Enforcer對象添加RuleDefault對象,則可以使用如下的方式。首先,創(chuàng)建一個(gè)Enforcer對象,然后創(chuàng)建一個(gè)或多個(gè)RuleDefault對象,接著調(diào)用Enforcer對象的register_defaults()方法或register_default()方法將RuleDefault對象注冊到Enforcer對象中。

[python]view plaincopy

fromoslo_configimportcfg

CONF?=?cfg.CONF

enforcer?=?policy.Enforcer(CONF,?policy_file=_POLICY_PATH)

base_rules?=?[

policy.RuleDefault('admin_required','role:admin?or?is_admin:1',

description='Who?is?considered?an?admin'),

policy.RuleDefault('service_role','role:service',

description='service?role'),

]

enforcer.register_defaults(base_rules)

enforcer.register_default(policy.RuleDefault('identity:create_region',

'rule:admin_required',

description='helpful?text'))

最后,為了可以使用oslo.policy相關(guān)的命令行更好的管理權(quán)限策略規(guī)則,可以在nova組件安裝配置文件setup.cfg文件中進(jìn)行添加如下配置:

[python]view plaincopy

[entry_points]

oslo.policy.enforcer?=

nova?=?nova.policy:get_enforcer

這樣,在使用命令行管理nova組件的權(quán)限控制策略時(shí),便可以根據(jù)oslo.policy.enforcer配置項(xiàng)找到nova組件的Enforcer對象。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容