===== Cephx ===== .. _cephx: Intro ----- The protocol design looks a lot like kerberos. The authorizer "KDC" role is served by the monitor, who has a database of shared secrets for each entity. Clients and non-monitor daemons all start by authenticating with the monitor to obtain tickets, mostly referreed to in the code as authorizers. These tickets provide both *authentication* and *authorization* in that they include a description of the *capabilities* for the entity, a concise structured description of what actions are allowed, that can be interpreted and enforced by the service daemons. Other references ---------------- - A write-up from 2012 on cephx as it existed at that time by Peter Reiher: :ref:`cephx_2012_peter` Terms ----- - *monitor(s)*: central authorization authority - *service*: the set of all daemons of a particular type (e.g., all OSDs, all MDSs) - *client*: an entity or principal that is accessing the service - *entity name*: the string identifier for a principal (e.g. client.admin, osd.123) - *ticket*: a bit of data that cryptographically asserts identify and authorization - *principal*: a client or daemon, identified by a unique entity_name, that shares a secret with the monitor. - *principal_secret*: principal secret, a shared secret (16 bytes) known by the principal and the monitor - *mon_secret*: monitor secret, a shared secret known by all monitors - *service_secret*: a rotating secret known by all members of a service class (e.g., all OSDs) - *auth ticket*: a ticket proving identity to the monitors - *service ticket*: a ticket proving identify and authorization to a service Terminology ----------- ``{foo, bar}^secret`` denotes encryption by secret. Context ------- The authentication messages described here are specific to the cephx auth implementation. The messages are transferred by the Messenger protocol or by MAuth messages, depending on the version of the messenger protocol. See also :ref:`msgr2`. An initial (messenger) handshake negotiates an authentication method to be used (cephx vs none or krb or whatever) and an assertion of what entity the client or daemon is attempting to authenticate as. Phase I: obtaining auth ticket ------------------------------ The cephx exchange begins with the monitor knowing who the client claims to be, and an initial cephx message from the monitor to the client/principal.:: a->p : CephxServerChallenge { u64 server_challenge # random (by server) } The client responds by adding its own challenge, and calculating a value derived from both challenges and its shared key principal_secret.:: p->a : CephxRequestHeader { u16 CEPHX_GET_AUTH_SESSION_KEY } CephXAuthenticate { u8 1 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 } The monitor looks up principal_secret in database, and verifies the key is correct. If old_ticket is present, verify it is valid, and we can reuse the same global_id. (Otherwise, a new global_id is assigned by the monitor.):: a->p : CephxReplyHeader { u16 CEPHX_GET_AUTH_SESSION_KEY s32 result (0) } u8 encoding_version = 1 u32 num_tickets ( = 1) ticket_info # (N = 1) where:: 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 } 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 == mon_secret {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 } So: for each ticket, principal gets a part that it decrypts with its secret to get the session_key (CephxServiceTicket). And the CephxTicketBlob is opaque (secured by the mon secret) but can be used later to prove who we are and what we can do (see CephxAuthorizer below). The client can infer that the monitor is authentic because it can decrypt the service_ticket with its secret (i.e., the server has its secret key). Phase II: Obtaining service tickets ----------------------------------- Now the client needs the keys used to talk to non-monitors (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) } where:: 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 } The monitor validates the authorizer by decrypting the auth_ticket with ``mon_secret`` and confirming that it says this principal is who they say they are in the CephxAuthorizer fields. Note that the nonce random bytes aren't used here (the field exists for Phase III below). Assuming all is well, the authorizer can generate service tickets based on the CEPH_ENTITY_TYPE_* bits in the ``keys`` bitmask. The response looks like:: CephxResponseHeader { u16 CEPHX_GET_PRINCIPAL_SESSION_KEY s32 result (= 0) } u8 encoding_version = 1 u32 num_tickets ticket_info * N Where, as above,:: 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 } This concludes the authentication exchange with the monitor. The client or daemon now has tickets to talk to the mon and all other daemons of interest. Phase III: Opening a connection to a service -------------------------------------------- When a connection is opened, an "authorizer" payload is sent:: 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 } Note that prior to the Luminous v12.2.6 or Mimic v13.2.2 releases, the CephxAuthorize msg did not contain a challenge, and consisted only of:: CephxAuthorize msg { u8 1 u64 nonce # random from client } The server will inspect the auth_ticket CephxTicketBlob (by decrypting it with its current rotating service key). If it is a pre-v12.2.6 or pre-v13.2.2 client, the server immediately replies with:: s->p : {CephxAuthorizeReply reply}^session_key where:: CephxAuthorizeReply { u64 nonce_plus_one } Otherwise, the server will respond with a challenge (to prevent replay attacks):: s->p : {CephxAuthorizeChallenge challenge}^session_key where:: CephxAuthorizeChallenge { u64 server_challenge # random from server } The client decrypts and updates its CephxAuthorize msg accordingly, resending most of the same information as before:: p->s : CephxAuthorizer { u8 AUTH_MODE_AUTHORIZER (1) u64 global_id u32 service_id # CEPH_ENTITY_TYPE_* CephxTicketBlob auth_ticket {CephxAuthorize msg}^session_key } where:: CephxAuthorize msg { u8 2 u64 nonce # (new) random from client bool have_challenge = true u64 server_challenge_plus_one # server_challenge + 1 } The server validates the ticket as before, and then also verifies the msg nonce has it's challenge + 1, confirming this is a live authentication attempt (not a replay). Finally, the server responds with a reply that proves its authenticity to the client:: s->p : {CephxAuthorizeReply reply}^session_key where:: CephxAuthorizeReply { u64 nonce_plus_one } The client decrypts and confirms that the server incremented nonce properly and that this is thus a live authentication request and not a replay. Rotating service secrets ------------------------ Daemons make use of a rotating secret for their tickets instead of a fixed secret in order to limit the severity of a compromised daemon. If a daemon's secret key is compromised by an attacker, that daemon and its key can be removed from the monitor's database, but the attacker may also have obtained a copy of the service secret shared by all daemons. To mitigate this, service keys rotate periodically so that after a period of time (auth_service_ticket_ttl) the key the attacker obtained will no longer be valid.:: p->a : CephxRequestHeader { u16 CEPHX_GET_ROTATING_KEY } a->p : CephxReplyHeader { u16 CEPHX_GET_ROTATING_KEY s32 result = 0 } {CryptoKey service_key}^principal_secret That is, the new rotating key is simply protected by the daemon's rotating secret. Note that, as an implementation detail, the services keep the current key and the prior key on hand so that the can continue to validate requests while the key is being rotated.