token 是用戶的一種憑證,需拿正確的用戶名/密碼向 Keystone 申請才能得到。如果用戶每次都采用用戶名/密碼訪問 OpenStack API,容易泄露用戶信息,帶來安全隱患。所以 OpenStack 要求用戶訪問其 API 前,必須先獲取 token,然后用 token 作為用戶憑據訪問 OpenStack API
1.UUID認證原理
以UUID形式返回token,每次都需要到keystone認證,造成性能瓶頸
當用戶需要進行操作時(比如訪問nova創建虛擬機),用戶拿着有效的用戶名/密碼先去keystone認證,keystone返回給用戶一個token(即UUID)。之后用戶進行其他操作(如訪問nova),先出示這個token給nova-api,nova收到請求后,就用這個token去向keystone進行請求驗證。keystone通過比對token,以及檢查token的有效期,判斷token的有效性,最后返回給nova結果
缺陷:每次請求都要經過keystone進行驗證,造成性能瓶頸
1.用戶輸入用戶名密碼,發送給keystone
2.Keystone驗證用戶名密碼,並且生成token(UUID),發送給客戶端
3.客戶端緩存UUID token
4.客戶端發送具體的執行請求(nova boot)和UUID給keystone
5.Keystone從http請求中獲取token,並檢查token是否有效
6.Token有效,處理請求,並返回客戶端請求結果
7.Token失效,拒絕客戶端請求,返回401
UUID方式源碼分析:
UUID token 是長度固定為 32 Byte 的隨機字符串,由 uuid.uuid4().hex 生成
def _get_token_id(self, token_data): return uuid.uuid4().hex
生成的樣例:144d8a99a42447379ac37f78bf0ef608
2.PKI認證原理
以非對稱加密方式對token進行加密,同時在認證初期生成密鑰對(公鑰和私鑰)和證書
當用戶以token到相應服務組件請求服務時,該組件通過證書對token進行解密獲取用戶信息,以此與失效列表進行比對
也需要每次請求keystone獲取失效列表,但對性能影響較低
在keystone初始化時,keystone生成了CA的公鑰CA.pem和私鑰CA.key,同時keystone產生了自己的公鑰keystone.pub和私鑰keystone.key,然后將keystone.pub進行CA的簽名,生成keystone.pem
當用戶拿着用戶名/密碼去keystone認證后,keystone將用戶的基本信息通過keystone.key進行加密,並將密文作為token返還給用戶。當用戶拿着token發送請求時(例如訪問nova),nova拿到用戶token時,通過事先拿到keystone的證書keystone.pem(這一過程只需要進行一次)進行解密,獲取用戶信息
對於用戶的token,還需進行token的合法時間,以及token還是否存在進行判斷。所以當nova每一次拿到token后還需向keystone詢問一次token的失敗列表,檢查token是否失效。這一過程對於keystone的負載還是相當輕的,所以PKI還是有效解決了keystone性能瓶頸的問題
小結:OpenStack服務中的每一個API Endpoint都有一份keystone簽發的證書,失效列表和根證書。API不用在直接去keystone認證token是否合法,只需要根據keystone的證書和失效列表就可以確定token是否合法。但是這里還是會有每次都需要請求keystone去獲取失效列表的操作,不可避免
PKI的流程,首先需要使用 keystone-manage pki_setup命令生成CA及相關的令牌機制,其代碼如下所示:
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)) #DEFAULT_TOKEN_DIGEST_ALGORITHM=sha256
其中,‘token_data’是獲取的user、role、endpoint、catlog等信息集合,而最重要的語句是cms使用簽名生成token的過程:cms_sign_token,使用默認的sha256方式加密,處理過程使用process,進行數據的讀取、處理,
process = subprocess.Popen(['openssl', 'cms', '-sign', '-signer', signing_cert_file_name, '-inkey', signing_key_file_name, '-outform', 'PEM', '-nosmimecap', '-nodetach', '-nocerts', '-noattr', '-md', message_digest, ], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True)
最后output, err = process.communicate(data) 生成Token-id,這個過程涉及到openssl相關的加密技術
CMS token一般都超過1600個字節,樣例:
3.Fernet認證原理
Fernet采用對稱加密方式,以key的方式認證token,不需要存儲於數據庫
采用輪換key的方式,當key認證失敗即token過了有效期,簡化了token過期驗證
1.user在客戶端輸入用戶名密碼,發送給keystone
2.Keystone驗證用戶名密碼,並且生成token(UUID),發送給客戶端
3.客戶端緩存token(UUID)
4.客戶端發送具體的執行請求給openstack API
5.OpenStack API向 keystone請求token認證
6.Keystone從http請求中獲取token,並檢查token是否有效
7.Token有效,處理請求,並返回openstack api請求結果
8.Token失效,拒絕客戶端請求,返回401
當集群運行較長一段時間后,訪問其 API 會變得奇慢無比,究其原因在於 Keystone 數據庫存儲了大量的 token 導致性能太差,解決的辦法是經常清理 token。為了避免上述問題,社區提出了Fernet token,fernet 是當前主流推薦的token格式,它采用 cryptography 對稱加密庫(symmetric cryptography,加密密鑰和解密密鑰相同) 加密 token,具體由 AES-CBC 加密和散列函數 SHA256 簽名。Fernet 是專為 API token 設計的一種輕量級安全消息格式,不需要存儲於數據庫,減少了磁盤的 IO,帶來了一定的性能提升。為了提高安全性,需要采用 Key Rotation 更換密鑰
以上代碼表明,token 包含了 user_id,project_id,domain_id,methods,expires_at 等信息,重要的是,它沒有 service_catalog,所以 region 的數量並不影響它的大小。self.pack() 最終調用如下代碼對上述信息加密:
該token 的大小一般在 200 多 Byte 左右,樣例:
gAAAAABWfX8riU57aj0tkWdoIL6UdbViV-632pv0rw4zk9igCZXgC-sKwhVuVb-wyMVC9e5TFc7uPfKwNlT6cnzLalb3Hj0K3bc1X9ZXhde9C2ghsSfVuudMhfR8rThNBnh55RzOB8YTyBnl9MoQ XBO5UIFvC7wLTh_2klihb6hKuUqB6Sj3i_8
簡要敘述一下fernet采用 Key Rotation 更換密鑰的原理,默認的輪換長度是3,當以keystone-manage fernet-setup生成密鑰時,會看到0、1兩個索引表征,這分別是什么意思呢?
在此,需要提一下三個概念:
primary key(主密鑰)有且只有一個,名為為x,當前用於加密解密token
secondary key(次次密鑰)有x-1個,從Primary退役下來的,用於解密當初它加密過的token
staged key(次密鑰)有且只有一個,命名為0,准備下一個rotation時變為Primary key,可以解密token
那么上述0 表示的是staged key,1 表示的是primary key,
primary key相比較另外兩種key,它的索引最高,並且可以加密、也可以解密;
staged key 相較於secondary key,它更有機會變為primary key
AES256加密token,SHA256 HMAC驗證完整性,
只要Keystone具有訪問這些key的權限,token就不需要在keystone數據庫中存儲
fernet的數據性能最好,原因是它不需要后端持久化操作(采用 Key Rotation定期 更換密鑰,只要Keystone具有訪問這些key的權限,更新后的token就不需要在keystone數據庫中存儲,緩解了數據庫負載壓力),並且token的認證,使用的是密鑰進行解密,能夠直接得出token Data的信息,從而進行token的過期認證。它的失敗原因,只可能是token過期了,或者是token放到了cache緩存中,但是已經被回收了。歸根到底,還是token過期了
參考鏈接: