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

        众所周知,OpenStack使用基于角色的权限访问控制(RBAC),在RBAC中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。在一个组织中,角色是为了完成各种工作而创造,用户则依据它的责任和资格来被指派相应的角色,用户可以很容易地从一个角色被指派到另一个角色。角色可依新的需求和系统的合并而赋予新的权限,而权限也可根据需要而从某角色中回收。角色与角色的关系可以建立起来以囊括更广泛的客观情况。为了更好的适应OpenStack的角色权限管理,oslo项目创建了oslo.policy子项目为所有OpenStack服务提供RBAC策略实施支持。本文将详细介绍oslo.policy的实现与使用。

1 策略规则表达式

在通用的策略引擎实现中,策略规则表达式一般包含一个目标和一个相关联的规则。具体示例如下:

"<target>": <rule>  

其中,target指定了正在执行策略的服务;通常,一个target指的是一个API调用。rule代表了具体的策略规则,一般可以用以下两种形式表示:一个使用新策略语法写成的字符串或者一个策略规则的列表。OpenStack推荐使用字符串格式,因为它更容易理解。

在策略语法中,每个规则检查都被指定为一个简单的"a:b"对形式,该"a:b"对通过匹配与之对应的类来执行检查访问权限。这些"a:b"对的类型与格式可以归纳为表1所示。

表1 策略语法类型与格式

类型格式

用户的角色role:admin

policy中定义的规则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

在字符串格式的策略规则表达式中,如果需要使用多个规则,可以使用连接运算符and或or,and表示与,or表示或。下面的例子表示允许角色为admin的用户或项目ID为%(project_id)s且角色为projectadmin的用户访问。

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

另外,策略规则表达式中还可以使用not运算符表取反。下面的例子表示允许项目ID为%(project_id)s且角色不是dunce的用户访问。

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

在策略规则表达式中,各运算符的优先级如表2所示。其中,数字越大代表优先级越高。

表2 策略规则表达式运算符优先级

优先级类型表达式

4组运算(...)

3逻辑否运算not ...

2逻辑与运算... and ...

1逻辑或运算... or ...

在列表形式的策略规则表达式中,使用"[]"表示逻辑与运算,在"[]"内的多个规则使用","连接,在进行权限检查时,只有"[]"中的所有规则都满足才可以通过检查;而同一级的不同"[]"表示逻辑或运算,用","连接,表示只要满足其中一个"[]"定义的规则即可访问;另外,使用"@"表示始终允许访问,使用"!"表示拒绝访问。基于此,上述示例表达式使用列表形式可以表示如下:

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

需要注意的是,如果一个规则定义为一个空列表[]或一个空字符串"",表示该target始终允许访问。

2 规则检查

在oslo.policy中,所有规则的封装与检查操作都定义在oslo_policy._checks模块中,该模块首先定义了一个BaseCheck抽象类,所有对规则检查的封装都需要继承该抽象类。在继承BaseCheck类时,每个规则检查类都需要覆写__str__()方法,用于显示该检查的含义;还需要覆写__call__()方法,在执行具体的检查操作时通过调用具体规则检查类的__call__()方法实现。接下来,本文首先介绍oslo.policy中定义了规则检查类。而针对"a:b"格式的策略规则表达式,oslo.policy定义了一个继承BaseCheck类的Check类来表示,并为其定义了kind属性表示该条规则检查的类型,即a,match属性表示具体匹配的值,即b。

2.1 GenericCheck

GenericCheck类通常用于匹配与API调用一起发送的属性。通过以下语法,策略引擎可以使用这些属性(位于策略规则表达式的右侧):

user_id:%(user.id)s

在上述语法中,右侧的值是一个字符串或者使用规定的Python字符串替换。可用的属性和值取决于使用公共策略引擎的程序。所有这些属性(与用户、API调用和上下文相关的)都可以相互检查,或对固定的常量进行检查。

GenericCheck类可以对通过令牌获取的以下几种用户属性执行策略检查:

user_id

domain_id和project_id(取决于token指定的范围)

给定的token范围内的角色role列表

2.2 特殊检查类

特殊检查类是相较于GenericCheck类而言的,特殊检查类可以提供更加灵活的检查机制。oslo.policy内置的特殊检查类包括RoleCheck、RuleCheck、HTTPCheck等。

RoleCheck类:该类用于检查提供的凭证中是否存在指定的角色。一个角色检查的表达式如下:

"role:<role_name>"  

RuleCheck类:该类用于通过名称引用另一个已定义的规则。这样,一个普通的规则可以定义为可重用的规则,然后在其他规则中引用该规则。它还适用于将一组检查定义为一个更具描述性的命名的情况,这样便于策略的可读性。一个规则检查的表达式如下,在这个示例中,将定义好的admin_required规则进行了重用。

"admin_required":"role:admin"

"<target>": "rule:admin_required"  

HTTPCheck类:该类用于向远程服务器发送HTTP请求,以确定检查结果。target和凭证将传递到远程服务器进行检查,如果远程服务器返回的响应结果为True,则表示该操作通过权限验证。HTTP检查的表达式如下,预期目标URL包含字符串格式的关键字,这个关键字是目标字典的key键。

"http:<target URI>"  

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

2.3 运算符检查类

oslo.policy为第1节中提到的各种运算符也提供了相应的检查类,主要包含以下几种检查类:

FalseCheck类:相当于运算符"!",即总是返回False,表示始终不允许访问。

TrueCheck类:相当于运算符"@",即总是返回True,表示始终允许访问。

NotCheck类:相当于取反运算符"not ..."。

AndCheck类:相当于逻辑与运算符"... and ..."。

OrCheck类:相当于逻辑或运算符"... or ..."。

除了上述的三类检查类,用户也可以自定义检查类。在自定义检查类时,首先需要继承BaseCheck类或Check类,然后覆写__str__()和__call__()方法:__str__()方法用于返回以此节点为根的Check树的字符串表示形式,用于打印;__call__()方法实现了具体的匹配算法。另外,还需要注意的是自定义检查类时还应该使用oslo_policy._checks模块下的register(name, func=None)装饰器将其缓存在检查类字典中,便于查找。

3 oslo.policy的权限检查实现原理

oslo.policy中权限检查的实现主要定义在oslo_policy.policy模块下。该模块中定义了用于保存规则,加载和检查规则以及定义策略的多个类。这些类的实现如下:

Rules类:该类用于缓存所有的规则,其可以直接处理default_rule的设置。该类提供了load(data, default_rule=None)和load(data, default_rule=None)可以从策略的YAML或JSON配置文件中加载所有规则;还提供了from_dict(rules_dict, default_rule=None)一个指定的字典中加载所有规则。

Enforcer类:该类负责加载和执行规则。该类提供了load_rules(force_reload=False)从该类的实例化对象绑定的策略文件路径加载所有规则,如果force_reload为True,表示从配置文件重新加载数据;提供了register_default(default)注册一个默认的RuleDefault对象,提供了register_defaults(defaults)注册一组RuleDefault对象;提供了enforce(rule, target, creds, do_raise=False, exc=None, *args, **kwargs)根据目标target和凭证检查规则的权限,该方法通常需要结合authorize(self, rule, target, creds, do_raise=False, exc=None, *args, **kwargs)装饰器使用,以保证待检查的策略规则已经被注册;该类还提供了check_rules(raise_on_violation=False)检查是否存在明显不正确的规则,如未定义的规则或循环引用的规则等。

RuleDefault类:该类用于定义一个权限检查策略,创建时需要指定名称name和值check_str,建议对该策略定义一个详细的说明description。

DocumentedRuleDefault类:该类用于定义一个policy-in-code的策略对象。该类的功能与RuleDefault类似,但它还需要一些与注册的策略规则有关的额外的数据。这样就可以根据这个类的属性来呈现与之对应的文档。最终,oslo.policy都会使用该类而弃用RuleDefault。创建该类的对象时,必须指定名称name、值check_str、描述信息description;另外,还需要定义一个operations属性,这个属性包含了每个API的URL和相应HTTP请求方法的字典。下面是一个operations属性的示例。

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

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

DeprecatedRule类:该类主要用于表示一个被废弃的策略或规则。

4 oslo.policy的使用方法

本节结合1-3节的内容以及nova组件介绍oslo.policy的使用方法。一般地,OpenStack其他组件可以直接使用oslo.policy库实现RBAC策略,也可以对oslo.policy库进行扩展,实现适合自身使用的RBAC策略。nova组件就是对oslo.policy的实现进行了扩展。

首先为了实现nova自身的需求,nova组件实现了一个检查类。

@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  

这个类用于判断用户是否是admin用户。接着,nova组件实现了一个初始化方法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)

该初始化方法创建了一个Enforcer对象,并将所有策略规则都加载到缓存中备用。然后,nova组件分别定义了两个用于检查规则的方法。

[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中的所有默认规则进行检查;而check_is_admin()方法只对nova自定义的IsAdminCheck类的规则进行检查。紧接着,nova组件定义了一个用户获取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

该方法通过读取配置文件或输入参数中的相关配置信息,获取一个Enforcer对象。因此,为了实现oslo.policy的功能,需要在nova组件的配置文件中添加如下的配置信息:

[python]view plaincopy

[DEFAULT]

output_file = policy-sample.yaml

namespace = nova

其中,namespace为oslo.policy添加了一个命名空间,这样可以隔离OpenStack的不同组件;output_file为默认策略规则文件的输出路径,当然你也可以自定义一个策略规则文件。

如果需要为定义的Enforcer对象添加RuleDefault对象,则可以使用如下的方式。首先,创建一个Enforcer对象,然后创建一个或多个RuleDefault对象,接着调用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相关的命令行更好的管理权限策略规则,可以在nova组件安装配置文件setup.cfg文件中进行添加如下配置:

[python]view plaincopy

[entry_points]

oslo.policy.enforcer =

nova = nova.policy:get_enforcer

这样,在使用命令行管理nova组件的权限控制策略时,便可以根据oslo.policy.enforcer配置项找到nova组件的Enforcer对象。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容