注意
本文档适用于 Ceph 的开发版本。
Cephx
简介
该协议设计与 Kerberos 非常相似。授权方“KDC”角色由 monitor 担任,它拥有每个实体的共享密钥数据库。客户端和非 monitor 守护进程都通过向 monitor 进行身份验证来开始,以获取票证,在代码中主要称为授权方。这些票证提供身份验证和授权,因为它们包含实体能力的描述,即允许执行哪些操作的简洁结构化描述,服务守护进程可以解释和执行这些操作。
其他参考资料
Peter Reiher 撰写于 2012 年的关于 Cephx 当时情况的文档:Cephx 身份验证协议的详细描述
术语
monitor(s):中央授权机构
服务:特定类型的所有守护进程的集合(例如,所有 OSD、所有 MDS)
客户端:正在访问服务的实体或主体
实体名称:主体的字符串标识符(例如 client.admin、osd.123)
票证:以加密方式断言身份和授权的数据片段
主体:一个客户端或守护进程,由唯一的 entity_name 标识,并与 monitor 共享一个密钥。
principal_secret:主体密钥,由主体和 monitor 知道的共享密钥(16 字节)
mon_secret:monitor 密钥,由所有 monitor 知道的共享密钥
service_secret:由服务类别的所有成员(例如,所有 OSD)知道的轮换密钥
auth ticket:向 monitor 证明身份的票证
service ticket:向服务证明身份和授权的票证
术语
{foo, bar}^secret 表示使用 secret 加密。
上下文
此处描述的身份验证消息特定于 cephx 身份验证实现。根据信使协议的版本,消息通过 Messenger 协议或 MAuth 消息传输。另请参阅 msgr2 协议 (msgr2.0 和 msgr2.1)。
初始(信使)握手协商要使用的身份验证方法(cephx、none、krb 或其他)以及客户端或守护进程尝试以哪个实体身份进行身份验证的断言。
第一阶段:获取身份验证票证
cephx 交换开始时,monitor 知道客户端声称是谁,以及 monitor 发送给客户端/主体的初始 cephx 消息。
a->p :
CephxServerChallenge {
u64 server_challenge # random (by server)
}
客户端通过添加自己的质询来响应,并计算一个值,该值来源于两个质询及其共享密钥 principal_secret。
p->a :
CephxRequestHeader {
u16 CEPHX_GET_AUTH_SESSION_KEY
}
CephXAuthenticate {
u8 2 # 2 means nautilus+
u64 client_challenge # random (by client)
u64 key = {client_challenge ^ server_challenge}^principal_secret # (roughly)
blob old_ticket # old ticket, if we are reconnecting or renewing
u32 other_keys # bit mask of service keys we want
}
在 Nautilus 之前,
CephXAuthenticate {
u8 1 # 2 means nautilus+
u64 client_challenge # random (by client)
u64 key = {client_challenge + server_challenge}^principal_secret # (roughly)
blob old_ticket # old ticket, if we are reconnecting or renewing
}
monitor 在数据库中查找 principal_secret,并验证密钥是否正确。如果 old_ticket 存在,则验证它是否有效,并且我们可以重用相同的 global_id。(否则,monitor 分配一个新的 global_id。)
a->p :
CephxReplyHeader {
u16 CEPHX_GET_AUTH_SESSION_KEY
s32 result (0)
}
u8 encoding_version = 1
u32 num_tickets ( = 1)
ticket_info # (N = 1)
加上(对于 Nautilus 及更高版本)
u32 connection_secret_len # in bytes
connection_secret^session_key
u32 other_keys_len # bytes of other keys (encoded)
other_keys {
u8 encoding_version = 1
u32 num_tickets
service_ticket_info * N # for each service ticket
}
其中
ticket_info {
u32 service_id # CEPH_ENTITY_TYPE_AUTH
u8 msg_version (1)
{CephXServiceTicket service_ticket}^principal_secret
{CephxTicketBlob ticket_blob}^existing session_key # if we are renewing a ticket,
CephxTicketBlob ticket_blob # otherwise
}
service_ticket_info {
u32 service_id # CEPH_ENTITY_TYPE_{OSD,MDS,MGR}
u8 msg_version (1)
{CephXServiceTicket service_ticket}^principal_secret
CephxTicketBlob ticket_blob
}
CephxServiceTicket {
CryptoKey session_key # freshly generated (even if old_ticket is present)
utime_t expiration # now + auth_mon_ticket_ttl
}
CephxTicketBlob {
u64 secret_id # which service ticket encrypted this; -1 == monsecret, otherwise service's rotating key id
{CephXServiceTicketInfo ticket}^mon_secret
}
CephxServiceTicketInfo {
CryptoKey session_key # same session_key as above
AuthTicket ticket
}
AuthTicket {
EntityName name # client's identity, as proven by its possession of principal_secret
u64 global_id # newly assigned, or from old_ticket
utime_t created, expires
AuthCapsInfo # what client is allowed to do
u32 flags = 0 # unused
}
因此:对于每张票证,主体都会获得一个部分,它使用其密钥解密以获取 session_key (CephxServiceTicket)。CephxTicketBlob 是不透明的(由 mon 密钥保护),但可以在以后用于证明我们是谁以及我们可以做什么(请参阅下面的 CephxAuthorizer)。
对于 Nautilus+,我们还包括服务票证。
客户端可以推断 monitor 是真实的,因为它可以使用其密钥解密 service_ticket(即服务器拥有其密钥)。
第二阶段:获取服务票证(Nautilus 之前版本)
现在客户端需要用于与非 monitor 通信的密钥(osd、mds、mgr)。
p->a :
CephxRequestHeader {
u16 CEPHX_GET_PRINCIPAL_SESSION_KEY
}
CephxAuthorizer authorizer
CephxServiceTicketRequest {
u32 keys # bitmask of CEPH_ENTITY_TYPE_NAME (MGR, OSD, MDS, etc)
}
其中
CephxAuthorizer {
u8 AUTH_MODE_AUTHORIZER (1)
u64 global_id
u32 service_id # CEPH_ENTITY_TYPE_*
CephxTicketBlob auth_ticket
{CephxAuthorize msg}^session_key
}
CephxAuthorize msg {
u8 2
u64 nonce # random from client
bool have_challenge = false # not used here
u64 server_challenge_plus_one = 0 # not used here
}
monitor 通过使用 mon_secret 解密 auth_ticket 来验证授权方,并确认它在 CephxAuthorizer 字段中说明该主体声称的身份。请注意,此处不使用 nonce 随机字节(该字段存在是为了下面的第三阶段)。
假设一切正常,授权方可以根据 keys 位掩码中的 CEPH_ENTITY_TYPE_* 位生成服务票证。
响应如下所示
CephxResponseHeader {
u16 CEPHX_GET_PRINCIPAL_SESSION_KEY
s32 result (= 0)
}
u8 encoding_version = 1
u32 num_tickets
ticket_info * N
如上所述,
ticket_info {
u32 service_id # CEPH_ENTITY_TYPE_{OSD,MGR,MDS}
u8 msg_version (1)
{CephXServiceTicket service_ticket}^principal_secret
CephxTicketBlob ticket_blob
}
CephxServiceTicket {
CryptoKey session_key
utime_t expiration
}
CephxTicketBlob {
u64 secret_id # which version of the (rotating) service ticket encrypted this
{CephXServiceTicketInfo ticket}^rotating_service_secret
}
CephxServiceTicketInfo {
CryptoKey session_key
AuthTicket ticket
}
AuthTicket {
EntityName name
u64 global_id
utime_t created, expires
AuthCapsInfo # what you are allowed to do
u32 flags = 0 # unused
}
这结束了与 monitor 的身份验证交换。客户端或守护进程现在拥有与 mon 和所有其他感兴趣的守护进程通信的票证。
第三阶段:打开到服务的连接
当打开连接时,会发送一个“授权方”负载
p->s :
CephxAuthorizer {
u8 AUTH_MODE_AUTHORIZER (1)
u64 global_id
u32 service_id # CEPH_ENTITY_TYPE_*
CephxTicketBlob auth_ticket
{CephxAuthorize msg}^session_key
}
CephxAuthorize msg {
u8 2
u64 nonce # random from client
bool have_challenge = false
u64 server_challenge_plus_one = 0
}
请注意,在 Luminous v12.2.6 或 Mimic v13.2.2 版本之前,CephxAuthorize 消息不包含质询,仅包含
CephxAuthorize msg {
u8 1
u64 nonce # random from client
}
服务器将检查 auth_ticket CephxTicketBlob(通过使用其当前轮换服务密钥解密它)。如果它是 pre-v12.2.6 或 pre-v13.2.2 客户端,服务器立即回复
s->p :
{CephxAuthorizeReply reply}^session_key
其中
CephxAuthorizeReply {
u64 nonce_plus_one
}
否则,服务器将回复一个质询(以防止重放攻击)
s->p :
{CephxAuthorizeChallenge challenge}^session_key
其中
CephxAuthorizeChallenge {
u64 server_challenge # random from server
}
客户端相应地解密并更新其 CephxAuthorize 消息,重新发送大部分与以前相同的信息
p->s :
CephxAuthorizer {
u8 AUTH_MODE_AUTHORIZER (1)
u64 global_id
u32 service_id # CEPH_ENTITY_TYPE_*
CephxTicketBlob auth_ticket
{CephxAuthorize msg}^session_key
}
其中
CephxAuthorize msg {
u8 2
u64 nonce # (new) random from client
bool have_challenge = true
u64 server_challenge_plus_one # server_challenge + 1
}
服务器像以前一样验证票证,然后还验证 msg nonce 具有其 challenge + 1,确认这是一个实时的身份验证尝试(而不是重放)。
最后,服务器回复一个向客户端证明其真实性的回复。如果模式需要,它还包括一些用于会话加密的熵。
s->p :
{CephxAuthorizeReply reply}^session_key
其中
CephxAuthorizeReply {
u64 nonce_plus_one
u32 connection_secret_length
connection secret
}
在 nautilus 之前,没有连接密钥
CephxAuthorizeReply {
u64 nonce_plus_one
}
客户端解密并确认服务器正确递增了 nonce,因此这是一个实时的身份验证请求,而不是重放。
轮换服务密钥
守护进程使用轮换密钥而不是固定密钥来处理其票证,以限制受损守护进程的严重性。如果守护进程的密钥被攻击者破坏,该守护进程及其密钥可以从 monitor 的数据库中删除,但攻击者也可能获得了所有守护进程共享的服务密钥副本。为了缓解这种情况,服务密钥会定期轮换,以便在一段时间后 (auth_service_ticket_ttl),攻击者获得的密钥将不再有效。
p->a :
CephxRequestHeader {
u16 CEPHX_GET_ROTATING_KEY
}
a->p :
CephxReplyHeader {
u16 CEPHX_GET_ROTATING_KEY
s32 result = 0
}
{CryptoKey service_key}^principal_secret
也就是说,新的轮换密钥仅受守护进程的轮换密钥保护。
请注意,作为实现细节,服务保留当前密钥和先前的密钥,以便在密钥轮换期间可以继续验证请求。