328
技術社區[雲棲]
cephx: ceph的認證和加密協議
Ceph作為一個分布式存儲係統,支持對象存儲、塊設備和文件係統。為了在網絡傳輸中防止數據被篡改,做到較高程度的安全性,加入了Cephx加密認證協議。其目的是識別身份,加密、驗證傳輸中的數據。
在ceph係統中,元數據保存在一個叫做ceph-mon的進程中,也可以稱為monitor節點,係統可以有多個monitor副本節點,用paxos保持數據一致性。 這裏不談paxos,也不談多個monitor節點,我們隻以單個monitor為例,重點說明cephx的實現。
monitor保存了係統中重要的元數據,例如每個用戶的key以及權限,這也是我們要重點談及的,至於其他osdmap, crush數據在這裏不涉及。
一個ceph係統主要由monitor, osd, client 這幾種類型的節點組成。monitor存放元數據,osd存放對象,client就是使用ceph係統的客戶端。
一個monitor裏存放著和認證相關的數據,簡單可以用表結構來描述:
名稱 |
key |
Caps(權限) |
client.admin |
xxxxxxyyyyy |
osd allow rw, mon allow rw |
osd.1 |
aaaaaaaaaaaaa |
osd allow rw |
osd.2 |
bbbbbbbbbbbbb |
osd allow rw |
client和osd 必須先連接到monitor進行認證。client和osd都有一個叫做monclient的模塊負責認證和密鑰交換。而monitor上有一個AuthMonitor的paxos服務模塊負責與monclient對話,cephx協議的實現則位於ceph源代碼目錄src/auth/cephx,有好幾個模塊負責。
Cephx是一種對稱密鑰加密協議,加密算法使用AES,並包含臨時密鑰生成和替換。每個client和osd都在本地有一個密鑰,該密鑰的副本同樣存在於monitor上。
Ceph裏麵每一個節點都會使用一個名稱,類型是EntityName, 例如:client.admin, osd.1等。而每一個節點連接到monitor以後,monitor都會為其生成一個global id,代表這個節點在整個ceph 係統中的全局id。
monclient連接monitor的會話是為了獲得session key、ticket以及臨時密鑰(rotating key)。
ceph 係統裏麵有auth認證服務,osdmap服務,還有文件係統的mds服務等。當節點之間彼此通訊時,剛連接開始時,使用ticket表明身份,使用rotating key加密加密解密ticket和session key, 驗明身份後,後續通訊使用session key加、解密數據包。
在monclient裏,對每一種服務monclient都保存有從monitor獲得的數據:與某類型節點網絡連接時的session key,與某類型節點網絡連接對應的ticket, 與該種服務對應的rotating key.
cephx認證大致步驟如下:
monclient向monitor 發起連接,獲得與monitor 通訊時的AUTH service的session key以及ticket。
認證信息是以後通訊中識別身份的憑據,其結構如下:
struct AuthTicket {
EntityName name;
uint64_t global_id; /* global instance id */
uint64_t auid;
utime_t created, renew_after, expires;
AuthCapsInfo caps;
__u32 flags;
};
內容依次是發起連接的實體名稱,全局id、 用戶id、一些和ticket生存周期有關的時間,以及權限。
struct CephXServiceTicketInfo {
AuthTicket ticket;
CryptoKey session_key;
};
我們在這裏稱CephxServiceTicketInfo為ticket,它包含認證信息以及session key。
通過了與monitor的認證以後,client就可以從monitor獲取與每一種service類型的相關的session key 和ticket,例如與osd服務和mds服務相關的session key和ticket, ticket的name和global_id,auid是相同的,其他存活期時間信息和權限則是不同的,client在每一種service上可以有不同的權限。其中ticket數據對client是透明的,是被相關service的臨時密鑰加密的,其內容隻有monitor和相關的service會去解密。
對於每一種service,它們的monclient除了上麵的內容,還會定時從monitor獲取它們自身這種service相關的臨時密鑰,主要數據結構如下:
一個會過期的密鑰:
struct ExpiringCryptoKey {
CryptoKey key;
utime_t expiration
};
一個對過期密鑰的管理器:
struct RotatingSecrets {
map<uint64_t, ExpiringCryptoKey> secrets;
version_t max_ver;
};
管理器包含3個密鑰,依次是過去的、現在的和將來的,map的key是一個臨時密鑰id (稱為secret id), 每次往裏麵添加一個密鑰,max_ver增加1,並且把最前麵個一密鑰刪除,始終保持3個密鑰,所以每次增加一個密鑰,他們的secret id都比以前的大,這樣就不存在secret id重複。
當client準備發起對osd的訪問時,就用對應的osd service的ticket去訪問osd, osd服務則用從monitor得到的臨時密鑰解密ticket,驗證其身份,然後從ticket取出session key, 因為客戶端已經有session key, 這樣雙方就有了相同的session key,以後通訊時client和osd就用這個密鑰加密和解密數據包,來驗證數據的正確性。隻要連接沒有斷開,session key就保持不變。
cephx認證具體步驟如下:
monclient和monitor的會話:
Step 1:
monclient: 發送 { protocol: 0, entity name, global id: 0 }
monintor: 保存entity name, 生成並保存64bit server challenge,
並把 server challenge 發送給對方。
Step 2:
monclient: 生成一個64bit client challenge, 用本地盤上的密鑰把server challenge和client challenge加密後,再用64比特為單位混淆生成一個64bit key, 並發送請求:
{ CEPHX_GET_AUTH_SESSION_KEY, client challenge, key, old ticket }
注意(第一次連接開始old ticket為無效數據)
monitor 接收到CEPHX_GET_AUTH_SESSION_KEY請求, 把得到的client challenge和存在自己內存裏的server challenge用client對應的key加密後混淆生成一個64bit key, 並與傳過來的key比較,如果不相等,則認證不通過。 (注意,monitor在自身數據庫存有對方的key) ,解密傳過來的old ticket, 得到 CephXServiceTicketInfo結構。從密鑰庫取出client對應的身份信息(密鑰,權限)。 生成新的ticket: (創建的時間,存活時間,global id, auid) ,生成與本monserver對話的session key, 從keystore獲取對應於AUTH service 的臨時密鑰rotating key(secret 密鑰, secret id密鑰id).
生成CephXServiceTicket結構(session key, ticket的存活時間), 用client在密鑰庫中的密鑰加密。生成 CephXTicketBlob, 其中包含臨時密鑰secret id 和 CephXServiceTicketInfo, 而CephxServiceTicketInfo包含 : (session key, ticket, 權限),其中CephxServiceTicketInfo用對應的臨時密鑰secret加密。注意Client不在乎ticket是什麼內容,因為這個作為一張票子是給通訊對方的,自己並不需要解釋什麼。如果上次成功解密 old ticket info,則使用old tick info中的session key加密整個CephXTicketBlob結構。
把經過上述處理後的CephxServiceTicket和CephxTicketBlob一起發送給monclient。
Step 3:
monclient 用自身本地盤上的密鑰解密 CephxServciceTicket,在獲得CephxTicketBlob時,如果CephxTicketBlob是經過加密的,則用客戶端當前的session key解密CephxTicketBlob。
保存CephxServiceTicket中的session key為最新的session key,保存CephXServiceTicket中session key的存活時間。
lient 如果需要其他服務的密鑰,則發起CEPHX_GET_PRINCIPAL_SESSION_KEY請求。
首先生成內容HEADER, 包括(global id, service id, CephxServiceTicketBlob),其中service id就是AUTH認證服務的id,然後生成CephXServiceTicketRequest結構, 內容包括bitmask,每一位代表一種服務類型,例如位掩碼中可以包含OSD, MDS。
生成CephXAuthorize結構, 內容包含隨機數nonc,並用session key 加密CephxAuthorize.
monclient發送:Header 、 CephxAuthorize 、 CephxServiceTicketRequest。
Monitor讀取Header, CephxServiceTickBlob,用CephxServiceTicketBlob中指定的secret id,從內存中獲得臨時密鑰secret,用secret解密CephxServiceTicketBlob, 獲得CephXServiceTicketInfo, 檢驗Header中的global id是否和CephxServiceTickeInfo中的global id相同,不相同就失敗。
用CephxServiceTicketInfo中的session key解密CephxAuthorize.
生成CephXAuthorizeReply , 內容包含CephxAuthoriz的nonce +1.
解碼 CephxServiceTicketRequest, 對其中的bitmask對應的每一種service,過程如下:
生成與該服務對話的session key
從keystore獲取對應於該service的臨時密鑰(secret, secret id).
生成 CephXServiceTicket(session key, ticket的存活時間),
用剛才解密的CephxServiceTicketInfo中的session key加密之。
生成 CephXTicketBlob, 其中包含 (secret id 和 CephXServiceTicketInfo)
而CephXServiceTicketInfo代表代表一張票子(ticket), 其中包含 : (剛才生成的與該service對話的session key, ticket, 權限),其中CephxServiceTicketInfo用服務對應的臨時密鑰secret加密. ticket繼承了step 2中的大部分內容,但是權限項目是對應的service中該client的權限。使用解密的CephxServiceTickt中的session key加密整個CephXTicketBlob.
把經過上述處理後的CephxServiceTicket和CephxTicketBlob一起發送給client.
monclient:對每一種service服務,過程如下:
用與monitor對話的session key解密CephxServciceTicket,如果CephxTicketBlob時經過密鑰
加密的,則用這個session key解密CephxTicketBlob。保存CephxServiceTicket中的session key為最新的session key,保存CephXServiceTicket中session key的存活時間。
client 與 osd會話的建立:
ceph 使用消息通訊機製,其中messenger模塊負責通訊,以simple messenger為例:
起實現通訊的代碼位於src/msg/simple/Pipe.cc中,例如當發起一個連接時,Pipe::connect被調用,執行開始就區獲得一個認證信息:
get_authorizer最終會執行到Client的代碼中:
而build_authorizer最後會執行到CephxTicketManager中,該代碼取出對應於某service的認證處理代碼:
而該處理程序則提供認證信息:
我們看到它主要是打包global_id, service id 以及ticket和一個隨機數,然後用session key加密。
以上時連接方發起的,下麵看接收連接方時如何工作的:
接收連接方的入口在src/msg/simple/Pipe.cc的accept()函數:
而vierfy_authorizer函數最終會執行到:
我們看到這個函數最終獲得對方的caps_info也即權限信息,對方的名稱,對方的全局id, 對方的session_key,以及uid.
而cephx_verify_authorizer做了什麼? 它用get_service_secret(service_id, ticket.secret_id, service_secret)得到臨時密鑰,然後decode_decrypt_enc_bl(cct, ticket_info, service_secret, ticket.blob, error)把票子解密。檢驗票子中的global id是否和明文發送過來的global id相同,不相同就會返回失敗。
最後它做一些nonce的解密,這不太重要,沒有什麼信息量。
雙方握手成功後,都會創建一個消息處理器,主要起到數據報的加密和檢驗。
以後雙方發送數據都用sign_message簽名:
而當前cephx 的sign_message依賴crc數據,隻使用頭尾幾個crc字段來計算簽名:
而接收數據方調用check_message_signature來驗證簽名:
最後更新:2017-06-08 16:31:53