keystone policy.json 的学习总结

keystone的policy.json文件位于:
/etc/keystone/policy.json


其内容如下:
1 {
2 "admin_required": "role:admin or is_admin:1",

34 "identity:get_project": "rule:admin_required",
35 "identity:list_projects": "rule:admin_required",
36 "identity:list_user_projects": "rule:admin_or_owner",
37 "identity:create_project": "rule:admin_required",
38 "identity:update_project": "rule:admin_required",
39 "identity:delete_project": "rule:admin_required",

41 "identity:get_user": "rule:admin_required",
42 "identity:list_users": "rule:admin_required",
43 "identity:create_user": "rule:admin_required",
44 "identity:update_user": "rule:admin_required",
45 "identity:delete_user": "rule:admin_required",
46 "identity:change_password": "rule:admin_or_owner",

此文件的格式为:“规则名”,再冒号空格,再“判定条件”。从规则名可以看出,规则名是代表的是某种动作,例如创建用户这个动作,对应动作名为“identity:create_user”,其判定条件是“rule:admin_required”。
判定条件“rule:admin_required”表示调用规则“admin_required”。“admin_required”在第2行,其判定条件是“role:admin or is_admin:1”,这就比较容易理解,如果当前用户是admin角色或者元数据中“is_admin”字段的值为1,就返回真。
判定条件中可以用“or”或者“and”等,甚至使用变量。
由于未找到官方对于配置policy.json的详细说明,本文的出现的规则语法,全部借鉴于网络。


从规则名可以知道,大概每个keystone的API在policy.json中都有对应的规则名。
经过验证,V2版本的API只验证规则“admin_required”的判定条件,而其它规则不生效。原因在于以下文件中:

/usr/lib/python2.7/site-packages/keystone/common/wsgi.py
185 class Application(BaseApplication):
274 def assert_admin(self, context):
299 creds['roles'] = user_token_ref.role_names
300 # Accept either is_admin or the admin role
301 self.policy_api.enforce(creds, 'admin_required', {})
assert_admin函数只调用了规则“admin_required”。


/usr/lib/python2.7/site-packages/keystone/identity/controllers.py
30 class User(controller.V2Controller):
31
32 @controller.v2_deprecated
33 def get_user(self, context, user_id):
34 self.assert_admin(context)
35 ref = self.identity_api.get_user(user_id)
36 return {'user': self.v3_to_v2_user(ref)}
每个动作的具体实现函数里,调用assert_admin函数进行判定条件的检查,所以只检查规则“admin_required”。
例如,如果想使V2的API不检查判定条件(即所有用户都能执行该动作),注释掉self.assert_admin(context)即可。
例如,如果想使V2的API换一个规则进行判定,把“admin_required”替换成别的规则。
操作系统中keystone命令默认是调用V2版本的API,故只生效规则“admin_required”。


V3版本的API支持policy.json中的所有规则。V3版本主要是多了“domain”的概念。V2中的tenant在V3中改名为project,任何一个project或者user只能归属于一个domain。
使用keystone相关命令创建的租户或用户,默认属于域“default”。


下面对policy.json的工作原理做一个总结。
其中最关键的文件是以下文件:

/usr/lib/python2.7/site-packages/keystone/openstack/common/policy.py
118 class Rules(dict):
Rules自动载入policy.json中的每条规则,返回字典对象rules。
它是动态的,对policy.json的修改是不需要重启keystone服务的(修改Python原代码需要重启keystone服务)。


/usr/lib/python2.7/site-packages/keystone/openstack/common/policy.py
174 class Enforcer(object):
262 def enforce(self, rule, target, creds, do_raise=False,
263 exc=None, *args, **kwargs):
296 try:
298 result = self.rules[rule](target, creds, self)
V3 API的每个动作执行时,都会调用enforce函数,判定是否符合policy.json中的规则,原因见下文。
rule是规则名,如“identity:create_endpoint”。
self.rules是keystone.openstack.common.policy.Rules类型,即包含policy.json每条规则的字典。
self.rules[rule]是keystone.openstack.common.policy.RuleCheck类型,即此处将调用RuleCheck函数,检查规则的判定条件。


/usr/lib/python2.7/site-packages/keystone/openstack/common/policy.py
830 class RuleCheck(Check):
831 def __call__(self, target, creds, enforcer):
834 try:
835 return enforcer.rules[self.match](target, creds, enforcer)
self.match是判定条件,即冒号右边的字符串。
enforcer.rules是keystone.openstack.common.policy.Rules类型,它根据self.match,决定调用何种Check函数,即enforcer.rules[self.match]可能是GenericCheck、RoleCheck、OrCheck、AndCheck等类型,然后调用对应函数。
OrCheck或AndCheck类型是当判定条件含有or或and才出现的类型。在OrCheck或AndCheck函数内部,把判定条件再拆解成GenericCheck或RoleCheck类型。例如,"domain_id:%(domain_id)s"或"role:service"。


/usr/lib/python2.7/site-packages/keystone/openstack/common/policy.py
867 class GenericCheck(Check):
880 try:
881 match = self.match % target
887 try:
889 leftval = ast.literal_eval(self.kind)
891 try:
892 leftval = creds[self.kind]
895 return match == six.text_type(leftval)
GenericCheck函数的逻辑比较复杂,涉及字典变量target和creds。


/usr/lib/python2.7/site-packages/keystone/common/controller.py
85 def protected(callback=None):
86 """Wraps API calls with role based access controls (RBAC).
152 self.policy_api.enforce(creds,
153 action,
154 utils.flatten_dict(policy_dict))
161 def filterprotected(*filters):
162 """Wraps filtered API calls with role based access controls (RBAC)." ""
193 self.policy_api.enforce(creds,
194 action,
195 utils.flatten_dict(target))
V3版本每个动作的具体实现函数中,都会调用protected或filterprotected函数,而这两个函数调用policy.py的enforce函数,传递了target(或policy_dict)变量。
utils.flatten_dict函数的作用是返回一个一维字典,即GenericCheck函数中的target变量是一维字典。


target是目标的意思,字典变量target中存储了操作对象的domain_id或user_id等。有以下几种方式传递方式:
1、列出domain中所有的用户,可以在URL中传递?domain_id=参数,如:
# curl http://controller:35357/v3/users?domain_id=660450adcc194c0bbf9e462bb21b0935 -H "X-Auth-Token:f469cb22b6384a5b8dd343e480fc7bba"|python -mjson.tool

2、列出用户的project信息,URL中传递了user_id,如:
# curl http://controller:35357/v3/users/735c4d1fc8eb4bf8b96ee6866b441d9d/projects -H "X-Auth-Token:f469cb22b6384a5b8dd343e480fc7bba"|python -mjson.tool

3、如创建用户、创建项目、删除用户等,传递了操作对象,如在哪个域创建用户,删除哪个用户等:
# curl -X POST http://controller:35357/v3/users -H "Content-type: application/json" -H "X-Auth-Token:22142d114ddc454a9fbf6d282793840e" -d '{"user": {"default_project_id": "c0d6c4a09b7649a19c394a6cd946f53f","domain_id": "660450adcc194c0bbf9e462bb21b0935","enabled": true,"name": "evecom001","password":"123456"}}'|python -mjson.tool

以上几种传递方式,其实就两类:一类是通过filterprotected函数传递;另一类是通过protected函数传递。总之target的内容是来自于http数据,若是使用curl命令调用API,则来自于URL或-d参数。


可以尝试在GenericCheck函数中添加打印target内容的代码,观察调用不同API时target内容的变化。以下是不同动作获得domain_id的不同方法:
list_users: domain_id=target['domain_id']
create_user: domain_id=target['user.domain_id']
delete_user: domain_id=target['target.user.domain_id']
list_projects: domain_id=target['domain_id']
create_project: domain_id=target['project.domain_id']
delete_project: domain_id=target['target.project.domain_id']
只有打印了才知道不同动作获得domain_id的方法是什么(或者去源代码找)。


知道了target的关键字,就可以编辑policy.json,判断当前执行操作的用户的domain_id,与被操作对象的domain_id是否一致:
"target_list_users": "domain_id:%(domain_id)s",
"target_create_user": "domain_id:%(user.domain_id)s",
"target_delete_user": "domain_id:%(target.user.domain_id)s",
"target_list_projects": "domain_id:%(domain_id)s",
"target_create_project": "domain_id:%(project.domain_id)s",
"target_delete_project": "domain_id:%(target.project.domain_id)s",

"identity:list_projects": "rule:target_list_projects",
"identity:create_project": "rule:target_create_project",
"identity:delete_project": "rule:target_delete_project",
"identity:list_users": "rule:target_list_users",
"identity:create_user": "rule:target_create_user",
"identity:delete_user": "rule:target_delete_user",


按照上述policy.json,当enforce函数检查规则“target_create_user”时,调用的是GenericCheck函数。
881 match = self.match % target
match变量的值为target['user.domain_id'],即被操作对象的domain_id。

892 leftval = creds[self.kind]
leftval变量的值为creds[domain_id],即执行操作者的domain_id。

895 return match == six.text_type(leftval)
最后enforce函数返回match是否与leftval相等的比较结果(True of False)。


至于字典变量creds,打印其关键字为:
'is_delegated_auth'
'access_token_id'
'user_id'
'roles'
'trustee_id'
'trustor_id'
'consumer_id'
'token'
'domain_id'
'project_id'
'trust_id'
有点特别奇怪,默认管理员账户admin的creds没有'domain_id'这个关键字。如果admin的creds有'domain_id'这个关键字,值应该为“default”。初步猜测是由于admin是用keystone命令创建造成的,因为keystone命令是调用V2 API,没有domain的概念,创建用户时,只指定了tenant,即V3中的project。换句话说,如果要判断用户的domain_id,创建这个用户的时候,要指定其domain_id。同理适用于project_id。


字典变量creds的关键字比较稳定,不像target的关键字不同的API都不一样。至于字典变量creds的内容如何产生,我未去寻找其源代码,因为想得通:多半是通过token就能知道是哪个用户,知道哪个用户,系统自然有它的信息。


除了keystone可以使用policy.json定义权限,其它openstack组件也有policy.json:
/etc/nova/policy.json
/etc/heat/policy.json
/etc/keystone/policy.json
/etc/glance/policy.json
/etc/neutron/policy.json
/etc/cinder/policy.json
/etc/ceilometer/policy.json

原文地址:https://www.cnblogs.com/endoresu/p/5018433.html