天天看點

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

client和osd 必須先連接配接到monitor進行認證。client和osd都有一個叫做monclient的子產品負責認證和密鑰交換。而monitor上有一個authmonitor的paxos服務子產品負責與monclient對話,cephx協定的實作則位于ceph源代碼目錄src/auth/cephx,有好幾個子產品負責。

cephx: ceph的認證和加密協定

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被調用,執行開始就區獲得一個認證資訊:

cephx: ceph的認證和加密協定

get_authorizer最終會執行到client的代碼中:

cephx: ceph的認證和加密協定

而build_authorizer最後會執行到cephxticketmanager中,該代碼取出對應于某service的認證處理代碼:

cephx: ceph的認證和加密協定

而該處理程式則提供認證資訊:

cephx: ceph的認證和加密協定

我們看到它主要是打包global_id, service id 以及ticket和一個随機數,然後用session key加密。

以上時連接配接方發起的,下面看接收連接配接方時如何工作的:

接收連接配接方的入口在src/msg/simple/pipe.cc的accept()函數:

cephx: ceph的認證和加密協定
cephx: ceph的認證和加密協定

而vierfy_authorizer函數最終會執行到:

cephx: ceph的認證和加密協定

我們看到這個函數最終獲得對方的caps_info也即權限資訊,對方的名稱,對方的全局id, 對方的session_key,以及uid.

cephx: ceph的認證和加密協定

而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的解密,這不太重要,沒有什麼資訊量。

cephx: ceph的認證和加密協定

雙方握手成功後,都會建立一個消息處理器,主要起到資料報的加密和檢驗。

cephx: ceph的認證和加密協定

以後雙方發送資料都用sign_message簽名:

cephx: ceph的認證和加密協定

而目前cephx 的sign_message依賴crc資料,隻使用頭尾幾個crc字段來計算簽名:

cephx: ceph的認證和加密協定

而接收資料方調用check_message_signature來驗證簽名:

cephx: ceph的認證和加密協定

繼續閱讀