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