天天看點

通路網址 token的格式_了解 Keystone 的四種 Token

Token 是什麼

通俗的講,token 是使用者的一種憑證,需拿正确的使用者名/密碼向 Keystone 申請才能得到。如果使用者每次都采用使用者名/密碼通路 OpenStack API,容易洩露使用者資訊,帶來安全隐患。是以 OpenStack 要求使用者通路其 API 前,必須先擷取 token,然後用 token 作為使用者憑據通路 OpenStack API。

通路網址 token的格式_了解 Keystone 的四種 Token

四種 Token 的由來

D 版本時,僅有 UUID 類型的 Token,UUID token 簡單易用,卻容易給 Keystone 帶來性能問題,從圖一的步驟 4 可看出,每當 OpenStack API 收到使用者請求,都需要向 Keystone 驗證該 token 是否有效。随着叢集規模的擴大,Keystone 需處理大量驗證 token 的請求,在高并發下容易出現性能問題。

于是 PKI( Public Key Infrastructrue ) token 在 G 版本運用而生,和 UUID 相比,PKI token 攜帶更多使用者資訊的同時還附上了數字簽名,以支援本地認證,進而避免了步驟 4。因為 PKI token 攜帶了更多的資訊,這些資訊就包括 service catalog,随着 OpenStack 的 Region 數增多,service catalog 攜帶的 endpoint 數量越多,PKI token 也相應增大,很容易超出 HTTP Server 允許的最大 HTTP Header(預設為 8 KB),導緻 HTTP 請求失敗。

顧名思義, PKIZ token 就是 PKI token 的壓縮版,但壓縮效果有限,無法良好的處理 token size 過大問題。

前三種 token 都會持久性存于資料庫,與日俱增積累的大量 token 引起資料庫性能下降,是以使用者需經常清理資料庫的 token。為了避免該問題,社群提出了 Fernet token,它攜帶了少量的使用者資訊,大小約為 255 Byte,采用了對稱加密,無需存于資料庫中。

UUID

UUID token 是長度固定為 32 Byte 的随機字元串,由 uuid.uuid4().hex 生成。

def _get_token_id(self, token_data):    return uuid.uuid4().hex
           

但是因 UUID token 不攜帶其它資訊,OpenStack API 收到該 token 後,既不能判斷該 token 是否有效,更無法得知該 token 攜帶的使用者資訊,是以需經圖一步驟 4 向 Keystone 校驗 token,并獲使用者相關的資訊。其樣例如下:

144d8a99a42447379ac37f78bf0ef608
           

UUID token 簡單美觀,不攜帶其它資訊,是以 Keystone 必須實作 token 的存儲和認證,随着叢集的規模增大,Keystone 将成為性能瓶頸。

PKI

通路網址 token的格式_了解 Keystone 的四種 Token

在闡述 PKI(Public Key Infrastruction) token 前,讓我們簡單的回顧 公開密鑰加密(public-key cryptography) 和 數字簽名 。公開密鑰加密,也稱為非對稱加密(asymmetric cryptography,加密密鑰和解密密鑰不相同),在這種密碼學方法中,需要一對密鑰,分别為公鑰(Public Key)和私鑰(Private Key),公鑰是公開的,私鑰是非公開的,需使用者妥善保管。如果把加密和解密的流程當做函數 C(x) 和 D(x),P 和 S 分别代表公鑰和私鑰,對明文 A 和密文 B 而言,數學的角度上有以下公式:

B = C(A, S)A = D(B, P)

其中加密函數 C(x), 解密函數 D(x) 以及公鑰 P 均是公開的。采用公鑰加密的密文隻能用私鑰解密,采用私鑰加密的密文隻能用公鑰解密。非對稱加密廣泛運用在安全領域,諸如常見的 HTTPS,SSH 登入等。

數字簽名又稱為公鑰數字簽名,首先采用 Hash 函數對消息生成摘要,摘要經私鑰加密後稱為數字簽名。接收方用公鑰解密該數字簽名,并與接收消息生成的摘要做對比,如果二者一緻,便可以确認該消息的完整性和真實性。

PKI 的本質就是基于數字簽名,Keystone 用私鑰對 token 進行數字簽名,各個 API server 用公鑰在本地驗證該 token。相關代碼簡化如下:

def _get_token_id(self, token_data):    try:        token_json = jsonutils.dumps(token_data, cls=utils.PKIEncoder)        token_id = str(cms.cms_sign_token(token_json,                                          CONF.signing.certfile,                                          CONF.signing.keyfile))        return token_id
           

其中 cms.cms_sign_token 調用 openssl cms –sign 對 token_data 進行簽名,token_data 的樣式如下:

{  "token": {    "methods": [ "password" ],    "roles": [{"id": "5642056d336b4c2a894882425ce22a86", "name": "admin"}],    "expires_at": "2015-12-25T09:57:28.404275Z",    "project": {      "domain": { "id": "default", "name": "Default"},      "id": "144d8a99a42447379ac37f78bf0ef608", "name": "admin"},    "catalog": [      {        "endpoints": [          {            "region_id": "RegionOne",            "url": "http://controller:5000/v2.0",            "region": "RegionOne",            "interface": "public",            "id": "3837de623efd4af799e050d4d8d1f307"          },          ......      ]}],    "extras": {},    "user": {      "domain": {"id": "default", "name": "Default"},      "id": "1552d60a042e4a2caa07ea7ae6aa2f09", "name": "admin"},    "audit_ids": ["ZCvZW2TtTgiaAsVA8qmc3A"],    "issued_at": "2015-12-25T08:57:28.404304Z"  }}
           

token_data 經 cms.cms_sign_token 簽名生成的 token_id 如下,共 1932 Byte:

MIIKoZIhvcNAQcCoIIFljCCBZICAQExDTALBglghkgBZQMEAgEwggPzBgkqhkiG9w0B......rhr0acV3bMKzmqvViHf-fPVnLDMJajOWSuhimqfLZHRdr+ck0WVQosB6+M6iAvrEF7v
           

PKIZ

通路網址 token的格式_了解 Keystone 的四種 Token

PKIZ 在 PKI 的基礎上做了壓縮處理,但是壓縮的效果極其有限,一般情況下,壓縮後的大小為 PKI token 的 90 % 左右,是以 PKIZ 不能友好的解決 token size 太大問題。

def _get_token_id(self, token_data):    try:        token_json = jsonutils.dumps(token_data, cls=utils.PKIEncoder)        token_id = str(cms.pkiz_sign(token_json,                                     CONF.signing.certfile,                                     CONF.signing.keyfile))        return token_id
           

其中 cms.pkiz_sign() 中的以下代碼調用 zlib 對簽名後的消息進行壓縮級别為 6 的壓縮。

compressed = zlib.compress(token_id, compression_level=6)
           

PKIZ token 樣例如下,共 1645 Byte,比 PKI token 減小 14.86 %:

PKIZ_eJytVcuOozgU3fMVs49aTXhUN0vAQEHFJiRg8IVHgn5OnA149JVaunNS3NYjoSU......W4fRaxrbNtinojheVICXYrEk0oPX6TSnP71IYj2e3nm4MLy7S84PtIPDz4_03IsOb2Q=
           

Fernet

通路網址 token的格式_了解 Keystone 的四種 Token

使用者可能會碰上這麼一個問題,當叢集運作較長一段時間後,通路其 API 會變得奇慢無比,究其原因在于 Keystone 資料庫存儲了大量的 token 導緻性能太差,解決的辦法是經常清理 token。為了避免上述問題,社群提出了 Fernet token ,它采用 cryptography 對稱加密庫(symmetric cryptography,加密密鑰和解密密鑰相同) 加密 token,具體由 AES-CBC 加密和散列函數 SHA256 簽名。 Fernet 是專為 API token 設計的一種輕量級安全消息格式,不需要存儲于資料庫,減少了磁盤的 IO,帶來了一定的 性能提升 。為了提高安全性,需要采用 Key Rotation 更換密鑰。

def create_token(self, user_id, expires_at, audit_ids, methods=None,                 domain_id=None, project_id=None, trust_id=None,                 federated_info=None):    """Given a set of payload attributes, generate a Fernet token."""    if trust_id:        version = TrustScopedPayload.version        payload = TrustScopedPayload.assemble(            user_id,            methods,            project_id,            expires_at,            audit_ids,            trust_id)    ...    versioned_payload = (version,) + payload    serialized_payload = msgpack.packb(versioned_payload)    token = self.pack(serialized_payload)    return token
           

以上代碼表明,token 包含了 user_id,project_id,domain_id,methods,expires_at 等資訊,重要的是,它沒有 service_catalog,是以 region 的數量并不影響它的大小。self.pack() 最終調用如下代碼對上述資訊加密:

def crypto(self):    keys = utils.load_keys()    if not keys:        raise exception.KeysNotFound()    fernet_instances = [fernet.Fernet(key) for key in utils.load_keys()]    return fernet.MultiFernet(fernet_instances)
           

該 token 的大小一般在 200 多 Byte 左右,本例樣式如下,大小為 186 Byte:

gAAAAABWfX8riU57aj0tkWdoIL6UdbViV-632pv0rw4zk9igCZXgC-sKwhVuVb-wyMVC9e5TFc7uPfKwNlT6cnzLalb3Hj0K3bc1X9ZXhde9C2ghsSfVuudMhfR8rThNBnh55RzOB8YTyBnl9MoQXBO5UIFvC7wLTh_2klihb6hKuUqB6Sj3i_8
           

如何選擇 Token

Token 類型UUIDPKIPKIZFernet

大小32 ByteKB 級别KB 級别約 255 Byte

支援本地認證不支援支援支援不支援

Keystone 負載大小小大

存儲于資料庫是是是否

攜帶資訊無user, catalog 等user, catalog 等user 等

涉及加密方式無非對稱加密非對稱加密對稱加密(AES)

是否壓縮否否是否

版本支援DGJK

Token 類型的選擇涉及多個因素,包括 Keystone server 的負載、region 數量、安全因素、維護成本以及 token 本身的成熟度。region 的數量影響 PKI/PKIZ token 的大小,從安全的角度上看,UUID 無需維護密鑰,PKI 需要妥善保管 Keystone server 上的私鑰,Fernet 需要周期性的更換密鑰,是以從安全、維護成本和成熟度上看,UUID > PKI/PKIZ > Fernet 如果:

  • Keystone server 負載低,region 少于 3 個,采用 UUID token。
  • Keystone server 負載高,region 少于 3 個,采用 PKI/PKIZ token。
  • Keystone server 負載低,region 大與或等于 3 個,采用 UUID token。
  • Keystone server 負載高,region 大于或等于 3 個,K 版本及以上可考慮采用 Fernet token。

繼續閱讀