探索 OpenStack 之(13):研究 Keystone


Keystone 是 OpenStack Identity Service 的項目名稱。本文就試着盡可能深入地研究 Keystone。

1. Keystone 的功能

做為 OpenStack 雲系統的入口,Keystone 提供了雲系統入口所需要的許多功能:

(1). 用戶身份驗證:系統得知道用戶是不是合法的用戶。為此,Keystone 需要對 user 進行管理和保存;管理用戶相關的 tenant、role、group 和 domain等;用戶 credential 的存放、驗證、令牌管理等。

(2). 服務目錄列表:用戶需要知道系統提供的服務目錄。為此,Keystone 得提供服務目錄的管理,包括 service、endpoint 等。

1.1 Keystone 管理的相關概念

(1)User 用戶:一個使用 OpenStack 雲服務的人、系統或者服務的數字表示。 用戶需要登錄,然后被分配 token 去訪問資源。用戶可以被分配到一個tenant。
(2)Credential 用戶證據:用來證明用戶身份的證據,可以是用戶名和密碼、用戶名和API key,或者一個 Keystone 分配的身份token。
(3)Authentication 身份驗證:驗證用戶身份的過程。Keystone 服務通過檢查用戶的 Credential 來確定用戶的身份。最開始,使用用戶名/密碼或者用戶名/API key作為credential。當用戶的credential被驗證后,Kestone 會給用戶分配一個 authentication token 供該用戶后續的請求使用。
(4)Token 令牌:一個用於訪問 OpenStack API 和資源的 alpha 數字字符串。一個 token 可能在任何時間被撤銷(revoke),因此其有效期是有限的。OpenStack中,token 是和特定的 tenant 綁定的,因此如果 user 如果訪問多個tenant的話他就需要多個tocken。
(5)Tenant 租戶:一個用於分組或者隔離資源的容器。一個 tenant 可能對應一個客戶、賬號、組織或者項目。在 OpenStack 中,用戶至少必須在一個tenant中。tenant 容器的可使用資源的限制成為 Tenant Quota,它包括tenant 內各子資源的quota。
(6)Service 服務:一個 OpenStack 服務,比如Nova、Swift或者Glance等。每個服務提供一個或者多個 endpoint 供用戶訪問資源以及進行操作。
(7)Endpoint 端點:一個網絡可訪問的服務地址,通過它你可以訪問一個服務,通常是個 URL 地址。不同 region 有不同的service endpoint。endpoint告訴也可告訴 OpenStack service 去哪里訪問特定的 servcie。比如,當 Nova 需要訪問 Glance 服務去獲取 image 時,Nova 通過訪問 Keystone 拿到 Glance 的 endpoint,然后通過訪問該 endpoint 去獲取Glance服務。我們可以通過Endpoint的 region 屬性去定義多個 region。Endpoint 該使用對象分為三類:

  • adminurl 給 admin 用戶使用
  • internalurl 給 OpenStack 內部服務使用來跟別的服務通信
  • publicurl 其它用戶可以訪問的地址

比如創建一個使用不同 IP 的 endpoint:

$ keystone endpoint-create \
 --region RegionOne \
 --service-id=1ff4ece13c3e48d8a6461faebd9cd38f \
 --publicurl='https://public-ip:8776/v1/%(tenant_id)s' \
 --internalurl='https://management-ip:8776/v1/%(tenant_id)s' \
 --adminurl='https://management-ip:8776/v1/%(tenant_id)s'
然后你可以配置 OpenStack service 使用另一個 service 的 endpoint 的 internalurl 去訪問另一個資源。

(8)Role 角色:一個 role 可看着一個ACL的集合。Keystone 中,分配給用戶的 token 包含了 role 列表。被訪問的服務會判斷訪問它的用戶的角色,以及每個role訪問資源或者操作的權限。系統默認使用 admin 和 _member_ role。User 驗證的時候必須帶有制定的 tenant,roles 會被分配到指定的 tenant。

(9)Policy 策略:OpenStack 對用戶的驗證除了 OpenStack 的身份驗證以外,還需要鑒別用戶對某個服務是否有訪問權限。Policy 機制就是用來控制某一個 User 在某個 Tenant 中某個操作的權限。這個 User 能執行什么操作,不能執行什么操作,就是通過 policy 機制來實現的。對於 Keystone 服務來說,policy 就是一個json 文件,默認是 /etc/keystone/policy.json。通過配置這個文件,Keystone Service 實現了對 User 的基於用戶角色的權限管理。

這些Keystone 管理對象之間的關系:

1.2 Keystone 管理這些概念的方法

組件名稱 管理對象 生成方法 保存方式 配置項
identity user,以及 user group - sql, kvs, ldap

[identity]

driver = keystone.identity.backends.[sql|kvs|ldap].Identity

token 用戶的臨時 token pki,pkiz,uuid sql, kvs,memcached

[token]

driver = keystone.token.persistence.backends.[sql|kvs|memcached].Token

provider=keystone.token.providers.[pkiz|pki|uuid].Provider
credential EC2 credential   sql

[credential]

driver = keystone.credential.backends.sql.Credential

catalog region,service,endpoint   sql|kvs| template

[catalog]

driver = keystone.catalog.backends.[sql|kvs| template].Catalog

assignment tenant,domain,role 以及它們與 user 之間的關系 external, password, token  

[assignment]

methods = external, password, token

password = keystone.auth.plugins.password.Password

trust trust  sql,kvs  

[trust]

driver = keystone.trust.backends.[ssql|kvs].Trust

policy Keystone service 的用戶鑒權策略    sql

[default]

policy_file = policy.json

[policy]

driver = keystone.policy.backends.sql.Policy

1.3 Keystone 代碼結構 

Keystone 的代碼結果和 OpenStack 的其它組件一樣,非常有層次感:

(1)bin 目錄下:

  • keystone-manage 是個 CLI 工具,它通過和 Keystone service 交互來做一些無法使用 Keystone REST API 來進行的操作,包括:
    • db_sync: Sync the database.
    • db_version: Print the current migration version of the database.
    • mapping_purge: Purge the identity mapping table.
    • pki_setup: Initialize the certificates used to sign tokens.
    • saml_idp_metadata: Generate identity provider metadata.
    • ssl_setup: Generate certificates for SSL.
    • token_flush: Purge expired tokens.
  • keystone-all 用於啟動 Keystone 的服務,下面有章節描述其服務啟動過程。

(2)keystone 目錄下:

    • 每個Keystone 組件,比如 catalog, token 等都有一個單獨的目錄。
    • 每個組件目錄中:
      • routes.py 定義了該組件的 routes (routes 見 探索 OpenStack 之(11):cinder-api Service 啟動過程分析 以及 WSGI / Paste deploy / Router 等介紹)。其中,identity 和 assignment 分別定義了 admin 和 public 使用的 routes,分別供 admin service 和 public service 使用。 
      • controller.py 文件定義了該組件所管理的對象,比如 assignment 的controller.py 文件定義了 Tenant、Role、Project 等類。
      • core.py 定了了兩個類 Manager 和 Driver。Manager 類對外提供該組件操作具體對象的方法入口; Driver 類定義了該組件需要其Driver實現類所提供的接口。
      • backend 目錄下的每個文件是每個具體driver 的實現
    • contrib 目錄包括resource extension 和 extension resource 的類文件

1.4 Keystone 服務啟動

/usr/bin/keystone-all 會啟動 keystone 的兩個service:admin and main,它們分別對應 /etc/keystone/keystone-paste.ini 文件中的兩個composite:

[composite:main]
use = egg:Paste#urlmap
/v2.0 = public_api
/v3 = api_v3
/ = public_version_api

[composite:admin]
use = egg:Paste#urlmap
/v2.0 = admin_api
/v3 = api_v3
/ = admin_version_api

可見 admin service 提供給administrator 使用;main 提供給 public 使用。它們分別都有 V2.0 和 V3 版本,只是目前的 keystone Cli 只支持 V2.0. 本文的分析以 V2.0 為對象,最后會看看兩個版本你的區別。比較下 admin 和 public:

名稱 middlewares factory 功能區別
admin 比 public 多 s3_extension keystone.service:public_app_factory

從 factory 函數來看, admin service 比 public service 多了 identity 管理功能, 以及 assignment 的admin/public 區別:

1. admin 多了對 GET /users/{user_id} 的支持,多了 get_all_projects, get_project,get_user_roles 等功能

2. keystone 對 admin service 提供 admin extensions, 比如 OS-KSADM 等;對 public service 提供 public extensions。

簡單總結一下, public service 主要提供了身份驗證和目錄服務功能;admin service 增加了 tenant、user、role、user group 的管理功能。

public

sizelimit url_normalize build_auth_context token_auth admin_token_auth xml_body_v2

json_body ec2_extension user_crud_extension

keystone.service:admin_app_factory

 

/usr/bin/keystone-all 會啟動 admin 和 public 兩個 service,分別綁定不同 host 和 端口。默認的話,綁定host 都是 0.0.0.0; admin 的綁定端口是 35357 (admin_port), public 的綁定端口是 5000 (public_port)。因此,給 admin 使用的 OS_AUTH_URL 為 http://controller:35357/v2.0, 給 public 使用的 OS_AUTH_URL=http://controller:5000/v2.0.

2. Keystone 中的幾個重點

2.1 用戶的 username/password 身份驗證和 token 生成

HTTP Verb:POST /tokens

HTTP params: {'auth': {u'tenantName': u'admin', u'passwordCredentials': {u'username': u'admin', u'password': u'1111'}}}

method: <bound method Auth.authenticate of <keystone.token.controllers.Auth object at 0x7fb58f611610>>

基本步驟:

(3)通過 user name 獲取 user id,最終會從 identity backend 中獲取到。

(6)校驗用戶的 password 和 identity backend 中的 password 的一致性。sql backend 的話,直接比較兩個值。

(8)獲取 project info,接下來檢查 domain,project,tenant 是否都是 enabled。

(19)獲取 catalog 和 roles。

(22)使用  user_ref,tenant_ref,metadata_ref,expiry,audit_id 構造 token_auth_data。

user_user_ref: {'name': u'admin', 'domain_id': u'default', 'enabled': True, u'email': u'admin@admin.com', 'id': u'1dc0db32a936496ebfc50be54924a7cc'}, tenant_ref: {'domain_id': u'default', 'description': u'Admin Tenant', 'enabled': True, 'id': u'43f66bb82e684bbe9eb9ef6892bd7fd6', 'name': u'admin'},metadata_ref: {'roles': [u'ea26bd3b3d0b4ce187ab606cd83eff69', u'4135d78453fb4975a959cd860bacdca0']}, expiry: 2015-02-08 15:41:31.064022, bind: None, audit_id: None

(23)再基於 catalog,roles 和 token_auth_data,創建 token data,創建好了以后該 token 會被保存到 persistence backend 中。 token data其內容包括:

  • user data,比如name,domain id,id, role names 等
  • tenant data,包括id,name,enabled 等。
  • catalog data,包括catalog 數組。比如:
  • "endpoints": [
                        {
                            "adminURL": "http://controller:8776/v2/43f66bb82e684bbe9eb9ef6892bd7fd6",
                            "region": "regionOne",
                            "internalURL": "http://controller:8776/v2/43f66bb82e684bbe9eb9ef6892bd7fd6",
                            "id": "652eb96c0d2f41e0ab17f4b61a1b8ee3",
                            "publicURL": "http://controller:8776/v2/43f66bb82e684bbe9eb9ef6892bd7fd6"
                        }
                    ],
                    "endpoints_links": [],
                    "type": "volumev2",
                    "name": "cinderv2"
  • metadata,比如 is_admin 以及 role ids。其中,is_admin 僅僅在headers中的 token 等於 keystone.conf 中設置的 admin_token 時設置為true。
  • token data,比如issued_at,token id,expire 等
  • trust data

其中,token_id 是調用相應的 token provider 的 _get_token_id 方法生成的:

  • UUID:uuid.uuid4().hex
  • PKI:token_id = str(cms.cms_sign_token(jsonutils.dumps(token_data), CONF.signing.certfile,CONF.signing.keyfile))
  • PKIZ:token_id = str(cms.pkiz_sign(jsonutils.dumps(token_data),CONF.signing.certfile,CONF.signing.keyfile))

從中可見:

  • UUDI 的 token id 只是一個純粹的32位十六進制字符串
  • PKI (Public Key Infrastructure) 的 token id 是使用 cert file 和 key file 對 token data 加密的結果
  • PKIZ 的 token id 是使用 pkiz_sign 函數,使用 cert file 和 key file 對 token data 加密的結果。從下面的 code 可以看出,pkiz 生成的 token id 其實就是使用 zlib 對 PKI 生成的 token id 進行壓縮,然后加上 PKIZ_ 前綴而已。其原因應該是 PKI 的 token id 太長了。供結果估計,PKIZ 能壓縮 PKI 一半左右,感覺這壓縮率也不高。
  • def pkiz_sign(text, signing_cert_file_name, signing_key_file_name, compression_level=6):
        signed = cms_sign_data(text, signing_cert_file_name, signing_key_file_name, PKIZ_CMS_FORM)
        compressed = zlib.compress(signed, compression_level)
        encoded = PKIZ_PREFIX + base64.urlsafe_b64encode(
            compressed).decode('utf-8')
        return encoded

2.2 keystone 驗證及 keystonemiddleware filter

客戶端在調用 POST /tokens 后拿到返回結果,如果用戶名和密碼驗證成功,則拿到諸如user,tenant,token,metadata,catalog等數據。從 catalog 中找到要訪問的 service 的 endpoint 的 URL,然后在 headers 中放入 {X-Auth-Token,token id},就可以訪問該 service 了。service 的 WSGI App 在收到該 Request 后,首先要驗證該 token id 是否有效,該驗證一般都使用一個 keystonemiddleware 的 middleware filter 來完成,其 AuthProtocol.__call__ 是處理Request 的入口函數。其處理過程大致如下:

這篇文章 understanding-openstack-authentication-keystone-pki 仔細分析了UUID/PKI/PKIZ token id 的生成和驗證過程。 

2.3 Role 和 Policy 策略

上面 2.1 和 2.2 只是驗證 user 的 credential,至於 user 有沒有權限來執行某個操作則取決於 Role 和 基於 Roles 的鑒權。每個 OpenStack 組件分別實現了鑒權功能,Keystone 和 其它組件中的實現有一些不同。

Keystone API V3 與 V2 相比,對 policy 的支持有很多的增強。V2 的支持和 OpenStack 其它組件比如cinder,nova 等差不多,只支持基於 policy.json 文件的 policy 控制;而 V3 中,支持對 policy 的 CURD 操作,以及對 endpoint,service 等的 policy 操作等。

2.3.1 Keystone 中的 RBAC (Role Based Access Controll)

Keystone 在每個子模塊的 controller.py 文件中的各個控制類的方法上實施 RBAC。有幾種方法,比如:

@controller.protected()
def create_region(self, context, region)

@controller.filterprotected('type', 'name')
def list_services(self, context, filters)

或者直接在函數體中使用 self.assert_admin(context):

def get_services(self, context):
    self.assert_admin(context)
    service_list = self.catalog_api.list_services()
    return {'OS-KSADM:services': service_list}

其中,protected 和 filterprotected 在 /keystone/common/controller.py 中實現。

protected/filterprotected 最終會調用 /keystone/openstack/common/policy.py 中的 enforce 方法,具體見 2.3.2 (2)中的描述。keystone 與 openstack 其它component 中的 RBAC 實現的不同之處在於 keystone 中有時候需要做 token 驗證再做 policy 檢查。

2.3.2 Keystone V3 API 中的 policy 操作

支持的policy 操作:

  • POST /v3/policies 創建一個 policy
  • GET /v3/policies 獲取所有 policys
  • GET /v3/policys/{policy id} 獲取一個policy
  • PATCH /v3/policies/​{policy_id}​ 修改一個 policy
  • DELETE /v3/policies/​{policy_id}​ 刪除一個 policy 

2.3.3 Keystone V3 API 中的 Endpoint Policy Assignment

Keystone 的 V3 API 的 OS-ENDPOINT-POLICY 擴展資源提供了 API 來支持:

  • 給特定的 endpoint 分配 policy。 REST API: /endpoints/{endpoint_id}/OS-ENDPOINT-POLICY/policy
  • 給特定的 service 的所有 endpoint 分配 policy。 REST API:PUT/DELETE /policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}
  • 給指定 region 中的指定 service 的所有 endpoint 分配 policy。REST API:GET/HEAD/PUT/DELETE /policies/{policy_id}/OS-ENDPOINT-POLICY/services/{service_id}/regions/{region_id}

支持 policy 存放在 sql db 中。

它的實現在 /keystone/contrib/endpoint_policy 中。

2.3.4 Cinder 中的 RBAC (Role Based Access Controll)

(1)使用 /etc/cinder/policy.json 文件

(2)Volume/Backup/ConsistencyGroup 等 API 類中的接口函數上的 @wrap_check_policy 或者在函數體內顯式調用 check_policy(context, 'get', volume) 來判斷調用用戶的 role 是否滿足 policy.json 中定義的要求。比如 /volume/api.py 中的 delete 方法:

@wrap_check_policy
def delete(self, context, volume, force=False, unmanage_only=False)

def wrap_check_policy(func):
    """Check policy corresponding to the wrapped methods prior to execution. This decorator requires the first 3 args of the wrapped function  to be (self, context, volume)    """
    @functools.wraps(func)
    def wrapped(self, context, target_obj, *args, **kwargs):
        check_policy(context, func.__name__, target_obj)
        return func(self, context, target_obj, *args, **kwargs)
    return wrapped

def check_policy(context, action, target_obj=None):
    target = {'project_id': context.project_id, 'user_id': context.user_id,}
    target.update(target_obj or {})
    _action = 'volume:%s' % action
    cinder.policy.enforce(context, _action, target)

def enforce(self, rule, target, creds, do_raise=False, exc=None, *args, **kwargs):

    #rule:volume:get_all

    #target: {'project_id': u'43f66bb82e684bbe9eb9ef6892bd7fd6', 'user_id': u'1dc0db32a936496ebfc50be54924a7cc'},  

    #creds 中包括 user role 等信息

    self.load_rules() #重新讀取 policy.json 文件

    ...

    result = self.rules[rule](target, creds, self) #使用特定的 rule 來檢查 user 的權限

    ...

    return True or PolicyNotAuthorized(rule)

cinder 的 policy.json 文件片段:

{
    "context_is_admin": "role:admin",
    "admin_or_owner":  "is_admin:True or project_id:%(project_id)s",
    "default": "rule:admin_or_owner",
    "admin_api": "is_admin:True",

    "volume:create": "", #'volume' 是 API 的類別名稱,比如’volume‘,’snapshot‘等。任何role的經過驗證的用戶都可以調用該API。 "volume:get_volume_admin_metadata": "rule:admin_api", #'get_volume_admin_metadata' 是 API 函數名稱。該函數需要 is_admin:True。 "volume:delete_volume_admin_metadata": "rule:admin_api",
    "volume:extend": "",
    "volume:update_readonly_flag": "",
    "volume:retype": "",

這篇文章 HAVANA KEYSTONE中policy.json的解析過程 詳細分析了該 enfore 過程。

如果要添加新的 role 來限制 cinder api 的話,需要(1)在keystone 中創建新的 role (2)修改 cinder 的 policy.json 文件,修改policy條件 (3)設置 user 的role 到新的role.

其它模塊,比如 nova, glance 等,和 cinder 模塊類似,只是區分了 admin role 和 非 admin role 的用戶。

 

3. V3 版本 Keystone API

V3 與 V2 相比,Keystone 的 API 得到了很大的增強:

1. 增加了 Domain,Group,Policy, Catalog 等對象的操作API

2. 似乎又將 Tenant 改成了 Project

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM