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来验证签名: