注意

本文档适用于 Ceph 的开发版本。

Cephx

简介

该协议设计与 Kerberos 非常相似。授权方“KDC”角色由 monitor 担任,它拥有每个实体的共享密钥数据库。客户端和非 monitor 守护进程都通过向 monitor 进行身份验证来开始,以获取票证,在代码中主要称为授权方。这些票证提供身份验证授权,因为它们包含实体能力的描述,即允许执行哪些操作的简洁结构化描述,服务守护进程可以解释和执行这些操作。

其他参考资料

术语

  • 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

也就是说,新的轮换密钥仅受守护进程的轮换密钥保护。

请注意,作为实现细节,服务保留当前密钥和先前的密钥,以便在密钥轮换期间可以继续验证请求。

由 Ceph 基金会为您呈现

Ceph 文档是由非营利性 Ceph 基金会 资助和托管的社区资源。如果您希望支持这项工作和我们的其他努力,请考虑 立即加入