2019-01-25 08:50:16 +00:00
|
|
|
=====
|
|
|
|
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
|
2019-01-30 16:40:47 +00:00
|
|
|
- *service*: the set of all daemons of a particular type (e.g., all
|
|
|
|
OSDs, all MDSs)
|
2019-01-25 08:50:16 +00:00
|
|
|
- *client*: an entity or principal that is accessing the service
|
2019-01-30 16:40:47 +00:00
|
|
|
- *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
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
- *principal*: a client or daemon, identified by a unique entity_name,
|
|
|
|
that shares a secret with the monitor.
|
2019-01-30 16:40:47 +00:00
|
|
|
- *principal_secret*: principal secret, a shared secret (16 bytes)
|
|
|
|
known by the principal and the monitor
|
2019-01-25 08:50:16 +00:00
|
|
|
- *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
|
2019-01-30 16:40:47 +00:00
|
|
|
- *service ticket*: a ticket proving identify and authorization to a
|
|
|
|
service
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
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
|
2019-03-11 13:52:28 +00:00
|
|
|
messenger protocol. See also :ref:`msgr2-protocol`.
|
2019-01-25 08:50:16 +00:00
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
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.
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
The client responds by adding its own challenge, and calculating a
|
|
|
|
value derived from both challenges and its shared key
|
|
|
|
principal_secret.::
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
p->a :
|
|
|
|
CephxRequestHeader {
|
|
|
|
u16 CEPHX_GET_AUTH_SESSION_KEY
|
|
|
|
}
|
|
|
|
CephXAuthenticate {
|
2019-01-25 09:05:20 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
Prior to nautilus,::
|
|
|
|
|
|
|
|
CephXAuthenticate {
|
|
|
|
u8 1 # 2 means nautilus+
|
2019-01-25 08:50:16 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
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.)::
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
a->p :
|
|
|
|
CephxReplyHeader {
|
|
|
|
u16 CEPHX_GET_AUTH_SESSION_KEY
|
|
|
|
s32 result (0)
|
|
|
|
}
|
|
|
|
u8 encoding_version = 1
|
|
|
|
u32 num_tickets ( = 1)
|
|
|
|
ticket_info # (N = 1)
|
|
|
|
|
2019-01-25 09:05:20 +00:00
|
|
|
plus (for Nautilus and later)::
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-01-25 08:50:16 +00:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-01-25 09:05:20 +00:00
|
|
|
service_ticket_info {
|
|
|
|
u32 service_id # CEPH_ENTITY_TYPE_{OSD,MDS,MGR}
|
|
|
|
u8 msg_version (1)
|
|
|
|
{CephXServiceTicket service_ticket}^principal_secret
|
|
|
|
CephxTicketBlob ticket_blob
|
|
|
|
}
|
|
|
|
|
2019-01-25 08:50:16 +00:00
|
|
|
CephxServiceTicket {
|
|
|
|
CryptoKey session_key # freshly generated (even if old_ticket is present)
|
|
|
|
utime_t expiration # now + auth_mon_ticket_ttl
|
|
|
|
}
|
|
|
|
|
|
|
|
CephxTicketBlob {
|
2019-01-25 09:05:20 +00:00
|
|
|
u64 secret_id # which service ticket encrypted this; -1 == monsecret, otherwise service's rotating key id
|
2019-01-25 08:50:16 +00:00
|
|
|
{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
|
2019-01-30 16:40:47 +00:00
|
|
|
later to prove who we are and what we can do (see CephxAuthorizer
|
|
|
|
below).
|
2019-01-25 08:50:16 +00:00
|
|
|
|
2019-01-25 09:05:20 +00:00
|
|
|
For Nautilus+, we also include the service tickets.
|
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
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).
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
|
2019-01-25 09:05:20 +00:00
|
|
|
Phase II: Obtaining service tickets (pre-nautilus)
|
|
|
|
--------------------------------------------------
|
2019-01-25 08:50:16 +00:00
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
Now the client needs the keys used to talk to non-monitors (osd, mds,
|
|
|
|
mgr).::
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
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
|
2019-01-30 16:40:47 +00:00
|
|
|
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).
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
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.
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
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
|
2019-01-30 16:40:47 +00:00
|
|
|
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::
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
The client decrypts and updates its CephxAuthorize msg accordingly,
|
|
|
|
resending most of the same information as before::
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
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).
|
2019-01-25 08:50:16 +00:00
|
|
|
|
2019-01-25 09:05:20 +00:00
|
|
|
Finally, the server responds with a reply that proves its authenticity
|
|
|
|
to the client. It also includes some entropy to use for encryption of
|
|
|
|
the session, if it is needed for the mode.::
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
s->p :
|
|
|
|
{CephxAuthorizeReply reply}^session_key
|
|
|
|
|
|
|
|
where::
|
|
|
|
|
2019-01-25 09:05:20 +00:00
|
|
|
CephxAuthorizeReply {
|
|
|
|
u64 nonce_plus_one
|
|
|
|
u32 connection_secret_length
|
|
|
|
connection secret
|
|
|
|
}
|
|
|
|
|
|
|
|
Prior to nautilus, there is no connection secret::
|
|
|
|
|
2019-01-25 08:50:16 +00:00
|
|
|
CephxAuthorizeReply {
|
|
|
|
u64 nonce_plus_one
|
|
|
|
}
|
|
|
|
|
2019-01-25 09:05:20 +00:00
|
|
|
The client decrypts and confirms that the server incremented nonce
|
|
|
|
properly and that this is thus a live authentication request and not a
|
|
|
|
replay.
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
|
|
|
|
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.
|
2019-01-30 16:40:47 +00:00
|
|
|
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.::
|
2019-01-25 08:50:16 +00:00
|
|
|
|
|
|
|
p->a :
|
|
|
|
CephxRequestHeader {
|
|
|
|
u16 CEPHX_GET_ROTATING_KEY
|
|
|
|
}
|
|
|
|
|
|
|
|
a->p :
|
|
|
|
CephxReplyHeader {
|
|
|
|
u16 CEPHX_GET_ROTATING_KEY
|
|
|
|
s32 result = 0
|
|
|
|
}
|
|
|
|
{CryptoKey service_key}^principal_secret
|
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
That is, the new rotating key is simply protected by the daemon's
|
|
|
|
rotating secret.
|
2019-01-25 08:50:16 +00:00
|
|
|
|
2019-01-30 16:40:47 +00:00
|
|
|
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.
|