policy

1. 首先还是先来了解一下什么是policy,它是用来做什么的

在openstack的用户管理中,有三个概念:Users, Tenants, Roles。简单来说,policy就是用来控制某一个User在某个Tenant中的权限的。这个User能执行什么操作,不能执行什么操作,就是通过policy机制来实现的。直观的看,policy就是一个json文件,位于/etc/[SERVICE_CODENAME]/policy.json中,每一个服务都有一个对应的policy.json文件,通过配置这个文件,实现了对User的权限管理。

另外就是还有一个角色(role)的概念,这个概念肯定都很熟悉了,是权限的集合,可以将role赋予某个user,使这个user拥有相应的权限,方便用户权限管理。policy.json文件可以在role的级别配置,不过默认的配置的角色只有admin,如果需要配置其他的角色,需要自己创建,然后在policy.json中进行配置。

接下来,看一下policy.json长什么样子:

{

"context_is_admin":  "role:admin",

"admin_or_owner":  "is_admin:True or project_id:%(project_id)s",

"default": "rule:admin_or_owner",

"compute:create": "role:admin",

"compute:create:attach_network": "",

"compute:create:attach_volume": "",

"compute:create:forced_host": "is_admin:True",

"compute:get_all": "",

......

}

复制代码

policy.json有两种写法

一种是每一行写成列表形式

另一种就是上面的例子,是写成字符串形式的

这里只说后面一种情况。

每一行可以分为两部分:冒号前面的叫做action,即用户执行的操作,冒号后面的叫rule,即用来根据当前的上下文(context),来判断前面的action是否能够由当前的user执行。policy做的主要工作,就是来解析这个rule的,要把这个字符串的rule,解析成相应的对象,在外部调用policy进行权限认证的时候,根据action映射到相应的rule对象,然后由这个对象判断是否能够执行这个action。

但是如上面的例子,像context_is_admin,admin_or_owner这样的action,怎么看都不像action,它们的确不是action,后面会讲到是怎么回事。

2. 如何实现

这部分功能的实现主要在nova/openstack/common/policy.py模块中实现,首先还是先来看看这个模块的整体结构图:


首先就是那个Rules类,它继承自dict,也就是说Rules是一个字典类型的,它的对象就是保存解析之后的policy.json文件得到的数据的,key保存的是action,value保存的是解析rule得到的对象。这个Rules对象被创建后,会赋值给模块中的_rules变量。

其次是BaseCheck体系类,它们就对应的是rule字符串解析之后得到的对象,即Rules对象的value保存的就是BaseCheck对象。这个类体系可以分为三类:

1)TrueCheck和FalseCheck:解析他们是最简单的,rule如果是空字符串或者是"@"的话,那么就直接对应的是TrueCheck对象,这个对象的返回值总为True;如果rule是"!"的话,那么就对应FalseCheck对象,总是返回False,如果rule不对应任何对象,那么它就最终对应FalseCheck对象

2)RuleCheck, RoleCheck, HttpCheck, GenericCheck:rule字符串中,冒号左边是"rule"的,都会解析成RuleCheck对象,冒号左边是"role"的都会解析成RoleCheck,冒号左边是"http"的都会解析成RoleCheck对象,如果是其他的情况,那么就对应GenericCheck对象,比如rule为is_admin:True的情况。

3)OrCheck, AndCheck, NotCheck:这些对象对应的是复合的rule,即用逻辑符号连接的几条规则,比如上面例子中的"is_admin:True or project_id:%(project_id)s",它对应的对象为OrCheck类型。上面两种情况的解析都很简单,难的就是这个复合rule的解析,费了一番功夫。上面类图中的ParseState类和ParseStateMeta类就是用来解析复合rule的。

如果不深究每条rule是怎么解析的话,看一个函数就够了,即模块中的_parse_check()函数,从这个函数中,就可以知道每条规则对应哪种对象:

# 真正的解析一个单一的rule,将它由冒号分隔开,得到kind和match,根据kind值,在_check查找对应的Check对象,

# 然后以kind,match为参数,调用Check对象的__call__()返回最终的结果:真或假

def _parse_check(rule):

"""

Parse a single base check rule into an appropriate Check object.

"""

# Handle the special checks

if rule == '!':

return FalseCheck()

elif rule == '@':

return TrueCheck()

try:

kind, match = rule.split(':', 1)

except Exception:

LOG.exception(_("Failed to understand rule %(rule)s") % locals())

# If the rule is invalid, we'll fail closed

return FalseCheck()

# Find what implements the check

if kind in _checks:

return _checks[kind](kind, match)#这里竟然调用的是Check的__init__(),把kind和match赋值给Check中的kind和match

elif None in _checks:

return _checks[None](kind, match)

else:

LOG.error(_("No handler for matches of kind %s") % kind)

        return FalseCheck()

复制代码

如果要深究每条rule是如何解析的话,请移步这里:

最后,再来说一下转换成的这些对象有什么用,为什么要费这么大劲转换成对象,直接用字符串的rule来判断不好吗,以及怎么用这些对象去判断用户的操作是否合法?

为什么要将字符串转换成对象?这个理由太好说了,就是为了抽象,抽象是为了方便,是为了以不变应万变,这就是面向对象的好处。比如此处的Check对象,就抽象出了kind和match成员变量,分别对应rule字符串的冒号左边和右边的内容。当用这些对象来判断用户操作是否合法时,是这样来使用的:result = _rules[action](target, creds),因为Rules类被创建后会赋值给_rules变量,所以这里的_rules变量就代表Rules对象,而Rules对象又是一个字典类型的,key是action,value是BaseCheck对象,所以就相当于是直接在调用BaseCheck对象的__call__()方法了,参数分别是target和creds,target是action要操作的目标,creds是当前的上下文环境,再结合Check对象中的kind和match,就可以根据相应的逻辑来判断这个操作是否合法了。举个简单的例子,看RoleCheck是如何来判断的:

@register("role")

class RoleCheck(Check):

def __call__(self, target, creds):

"""Check that there is a matching role in the cred dict."""

        return self.match.lower() in [x.lower() for x in creds['roles']]

复制代码

比如规则"role:admin",kind为"role",match为"admin",所以RoleCheck就是判断一下admin这个用户是否在creds上下文中,如果在,就返回真,不在返回假。

至于OrCheck等对象判断是否合法则更简单,在他们内部都维护了一个rules列表,存放的是每条单一规则对应的Check对象,在他们的__call__()方法中一个for循环,来判断,OrCheck为一真即真,AndCheck为一假即假。

上面还提到有些action看起来不像action的,的确,那样的规则,一般都会转换成GenericCheck对象,而RuleCheck对象的__call__()在判断用户操作是否合法时,是采用递归的方法来判断的,比如下面的例子:

"compute_extension:accounts": "rule:admin_api", #-->RuleCheck

"admin_api": "is_admin:True", #-->GenericCheck

复制代码

RuleCheck对象通过递归调用,最终调用了GenericCheck对象的__call__()方法,得出最终的结果。至于,为什么要这样做,我现在还不是很清楚。

3. 如何使用

如何使用上面已经说的差不多了,在外部只要调用nova/policy.py模块中的enforce()即可:

def enforce(context, action, target, do_raise=True):

init()#读取json文件,解析,并将解析的内容封装成一个Rules对象,然后把这个对象赋值给_rules变量

credentials = context.to_dict()

extra = {}

if do_raise:

extra.update(exc=exception.PolicyNotAuthorized, action=action)

    return policy.check(action, target, credentials, **extra)

复制代码

有意思的是每次调用enfore(),都会去重新读取一次policy.json文件,并且重新进行一次解析,所以,对json文件的修改,是起实时作用的,不需要重启任何服务,修改之后,只要调用enfore()就会起作用,这很方便。

4. 测试

说了这么多,不能光说不练啊,这里还是举个创建实例的例子,通过调试的手段,来具体看一下效果。测试的过程如下:

(1)修改程序

在nova/compute/api.py中的_check_create_policies()方法中添加如下几句代码:

print '*'*30

creds=context.to_dict()

print creds['roles']

print '*'*30

raise Exception

复制代码

即打印出当前上下文中的roles。然后抛出异常,停止。

(2)启动各项服务

(3)修改policy.json文件

将 "compute:create" : "" 这条规则修改为:"compute:create": "role:admin", 即将原来的任何角色的用户都可以创建实例的权限修改为只有admin角色的用户可以创建实例。

(4)创建实例,观看异常

$ source openrc demo demo

$ nova boot --flavor 1 --image 29936f2a-0e05-47e0-8d43-b9d579b107f9 --key-name pubkey-01 instance-01

ERROR: Policy doesn't allow compute:create to be performed. (HTTP 403) (Request-ID: req-bd0cda7e-2207-46af-becb-72a3a3bec598)

复制代码

看,报出了异常,说policy不允许compute:create被执行,说明修改的规则生效了。

看下日志中输出的当前上下文中的角色:

******************************

[u'Member', u'anotherrole']

******************************

如果使用admin角色的用户创建实例是没有问题的。日志输出如下:

******************************

[u'admin', u'KeystoneAdmin', u'KeystoneServiceAdmin']

******************************

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

推荐阅读更多精彩内容