注意

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

Cephx

简介

协议设计看起来很像kerberos。授权者“KDC”角色由监控器担任,它拥有每个实体的共享密钥数据库。客户端和非监控器守护进程都首先通过监控器进行认证以获取票据,在代码中主要称为授权者。这些票据既提供认证授权因为它们包含对实体的capabilities描述,一个简洁的结构化描述,允许执行哪些操作,可以被服务守护进程解释和执行。

其他参考

术语

  • 监控器(monitor): 中心授权机构

  • 服务: 特定类型所有守护进程的集合(例如,所有OSDs,所有MDSs)

  • 客户端: 访问服务的实体或主体

  • 实体名称: 主体字符串标识符(例如,client.admin,osd.123)

  • 票据: 一段数据,通过加密断言身份和授权

  • 主体: 客户端或守护进程,通过唯一的实体名称标识,与监控器共享密钥。

  • 主体密钥: 主体密钥,一个共享密钥(16字节),由主体和监控器知晓

  • mon_secret: 监控器密钥,一个所有监控器都知道的共享密钥

  • 服务密钥: 一个旋转密钥,服务类所有成员都知道(例如,所有OSDs)

  • 认证票据: 一个证明身份给监控器的票据

  • 服务票据: 一个证明身份和授权给服务的票据

术语

{foo, bar}^secret表示通过密钥加密。

背景

这里描述的认证消息是特定于cephx认证实现的。消息通过Messenger协议或MAuth消息传输,具体取决于Messenger协议的版本。另见msgr2协议(msgr2.0和msgr2.1).

初始(Messenger)握手协商要使用的认证方法(cephx与无或krb或其他)以及对客户端或守护进程试图认证为哪个实体的断言。

第一阶段:获取认证票据

Cephx交换以监控器知道客户端声称的身份,以及监控器向客户端/主体发送的初始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
}

监控器在数据库中查找principal_secret,并验证密钥是否正确。如果old_ticket存在,验证它是否有效,我们可以重用相同的global_id。(否则,监控器分配一个新的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)

plus(对于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, renew_after, expires
  AuthCapsInfo       # what client is allowed to do
  u32 flags = 0      # unused
}

所以:对于每个票据,主体使用其密钥解密以获取会话密钥(CephxServiceTicket)。CephxTicketBlob是不透明的(由mon密钥保护),但可以稍后用来证明我们的身份和我们可以做什么(见下文CephxAuthorizer)。

对于Nautilus+,我们还包含服务票据。

客户端可以推断监控器是经过认证的,因为它可以使用其密钥解密service_ticket(即服务器拥有其密钥)。

第二阶段:获取服务票据(预-nautilus)

现在客户端需要与非监控器(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
}

监控器通过解密auth_ticket来验证授权者mon_secret并确认它在CephxAuthorizer字段中说明这个主体是他们声称的身份。注意,这里不使用nonce随机字节(该字段存在于下面的第三阶段)。

假设一切顺利,授权者可以根据CEPH_ENTITY_TYPE_*位在keys位掩码中生成服务票据。

响应看起来像:

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, renew_after, expires
  AuthCapsInfo       # what you are allowed to do
  u32 flags = 0      # unused
}

这完成了与监控器的认证交换。客户端或守护进程现在有与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是否为其挑战+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,并且这是一个实时的认证请求,而不是重放。

旋转服务密钥

守护进程使用旋转密钥而不是固定密钥来限制受攻击守护程序的严重程度。如果守护程序的密钥被攻击者攻破,该守护程序及其密钥可以从监控器的数据库中删除,但攻击者可能还获得了所有守护程序共享的服务密钥的副本。为了减轻这种情况,服务密钥定期旋转,以便在一定时间(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 基金会资助和托管Ceph Foundation. 如果您想支持这一点和我们的其他工作,请考虑加入现在加入.