From f3a4166379b12d4a7bba667fe761e5b660552db1 Mon Sep 17 00:00:00 2001 From: Ilya Dryomov Date: Apr, 15 May 2021 08:40:32 +0800 copy-by: https://github.com/ceph/ceph/commit/f3a4166379b12d4a7bba667fe761e5b660552db1 * CVE-2021-20288: qa/standalone: default to disable insecure global id reclaim cephadm: set auth_allow_insecure_global_id_reclaim for mon on bootstrap mon/HealthMonitor: raise AUTH_INSECURE_GLOBAL_ID_RENEWAL[_ALLOWED] auth/cephx: ignore CEPH_ENTITY_TYPE_AUTH in requested keys auth/cephx: rotate auth tickets less often mon: fail fast when unauthorized global_id (re)use is disallowed auth/cephx: option to disallow unauthorized global_id (re)use auth/cephx: make cephx_decode_ticket() take a const ticket_blob auth/AuthServiceHandler: keep track of global_id and whether it is new auth/AuthServiceHandler: build_cephx_response_header() is cephx-specific auth/AuthServiceHandler: drop unused start_session() args mon/MonClient: drop global_id arg from _add_conn() and _add_conns() mon/MonClient: reset auth state in shutdown() mon/MonClient: preserve auth state on reconnects mon/MonClient: claim active_con's auth explicitly mon/MonClient: resurrect "waiting for monmap|config" timeouts Signed-off-by: Ilya Dryomov Reviewed-by: Sage Weil Subject: [PATCH] fix CVE 2021 20288 --- doc/rados/operations/health-checks.rst | 61 +++++++ qa/standalone/ceph-helpers.sh | 1 + src/auth/AuthClientHandler.cc | 2 +- src/auth/AuthClientHandler.h | 9 +- src/auth/AuthServiceHandler.cc | 42 +++++ src/auth/AuthServiceHandler.h | 45 ++++- src/auth/AuthSessionHandler.cc | 4 + src/auth/Crypto.cc | 5 +- src/auth/cephx/CephxClientHandler.cc | 6 - src/auth/cephx/CephxClientHandler.h | 6 +- src/auth/cephx/CephxKeyServer.cc | 78 ++++----- src/auth/cephx/CephxKeyServer.h | 26 +-- src/auth/cephx/CephxProtocol.cc | 6 +- src/auth/cephx/CephxProtocol.h | 20 ++- src/auth/cephx/CephxServiceHandler.cc | 166 ++++++++++++++----- src/auth/cephx/CephxServiceHandler.h | 20 ++- src/auth/none/AuthNoneClientHandler.h | 8 +- src/auth/none/AuthNoneServiceHandler.h | 17 +- src/auth/unknown/AuthUnknownServiceHandler.h | 10 +- src/common/legacy_config_opts.h | 2 + src/common/options.cc | 34 +++- src/mon/AuthMonitor.cc | 14 +- src/mon/HealthMonitor.cc | 38 +++++ src/mon/MonClient.cc | 24 ++- src/mon/MonClient.h | 4 +- src/mon/Monitor.cc | 63 +++++-- src/mon/MonitorDBStore.h | 2 +- src/mon/Session.h | 23 ++- 28 files changed, 578 insertions(+), 158 deletions(-) diff --git a/doc/rados/operations/health-checks.rst b/doc/rados/operations/health-checks.rst index c1e22004..4def3fdf 100644 --- a/doc/rados/operations/health-checks.rst +++ b/doc/rados/operations/health-checks.rst @@ -21,6 +21,67 @@ that are defined by ceph-mgr python modules. Definitions =========== +Monitor +------- + +AUTH_INSECURE_GLOBAL_ID_RECLAIM +_______________________________ + +One or more clients or daemons are connected to the cluster that are +not securely reclaiming their global_id (a unique number identifying +each entity in the cluster) when reconnecting to a monitor. The +client is being permitted to connect anyway because the +``auth_allow_insecure_global_id_reclaim`` option is set to true (which may +be necessary until all ceph clients have been upgraded), and the +``auth_expose_insecure_global_id_reclaim`` option set to ``true`` (which +allows monitors to detect clients with insecure reclaim early by forcing them to +reconnect right after they first authenticate). + +You can identify which client(s) are using unpatched ceph client code with:: + + ceph health detail + +Clients global_id reclaim rehavior can also seen in the +``global_id_status`` field in the dump of clients connected to an +individual monitor (``reclaim_insecure`` means the client is +unpatched and is contributing to this health alert):: + + ceph daemon mon. sessions + +We strongly recommend that all clients in the system are upgraded to a +newer version of Ceph that correctly reclaims global_id values. Once +all clients have been updated, you can stop allowing insecure reconnections +by setting the following option in the ``[mon]`` section of ``ceph.conf``:: + + auth_allow_insecure_global_id_reclaim = false + +Although we do NOT recommend doing so, you can also disable this warning indefinitely +by setting the following option in the ``[mon]`` section of ``ceph.conf``:: + + mon_warn_on_insecure_global_id_reclaim = false + +AUTH_INSECURE_GLOBAL_ID_RECLAIM_ALLOWED +_______________________________________ + +Ceph is currently configured to allow clients to reconnect to monitors using +an insecure process to reclaim their previous global_id because the setting +``auth_allow_insecure_global_id_reclaim`` is set to ``true``. It may be necessary to +leave this setting enabled while existing Ceph clients are upgraded to newer +versions of Ceph that correctly and securely reclaim their global_id. + +If the ``AUTH_INSECURE_GLOBAL_ID_RECLAIM`` health alert has not also been raised and +the ``auth_expose_insecure_global_id_reclaim`` setting has not been disabled (it is +on by default), then there are currently no clients connected that need to be +upgraded, and it is safe to disallow insecure global_id reclaim by setting the +following option in the ``[mon]`` section of ``ceph.conf``:: + + auth_allow_insecure_global_id_reclaim = false + +Although we do NOT recommend doing so, you can also disable this warning indefinitely +by setting the following option in the ``[mon]`` section of ``ceph.conf``:: + + mon_warn_on_insecure_global_id_reclaim_allowed = false + OSDs ---- diff --git a/qa/standalone/ceph-helpers.sh b/qa/standalone/ceph-helpers.sh index f12f0698..ac2ac180 100755 --- a/qa/standalone/ceph-helpers.sh +++ b/qa/standalone/ceph-helpers.sh @@ -461,6 +461,7 @@ function run_mon() { --pid-file=$dir/\$name.pid \ --mon-allow-pool-delete \ --mon-osd-backfillfull-ratio .99 \ + --mon-warn-on-insecure-global-id-reclaim-allowed=false \ "$@" || return 1 cat > $dir/ceph.conf <entity_name.get_type() && !this->global_id && + global_id_status == global_id_status_t::NONE); + + ldout(cct, 10) << __func__ << " entity_name=" << entity_name + << " global_id=" << global_id << " is_new_global_id=" + << is_new_global_id << dendl; + this->entity_name = entity_name; + this->global_id = global_id; + + return do_start_session(is_new_global_id, result, caps); +} + AuthServiceHandler *get_auth_service_handler(int type, CephContext *cct, KeyServer *ks) { switch (type) { diff --git a/src/auth/AuthServiceHandler.h b/src/auth/AuthServiceHandler.h index 4d8a6493..ae40adb7 100644 --- a/src/auth/AuthServiceHandler.h +++ b/src/auth/AuthServiceHandler.h @@ -24,21 +24,54 @@ class CephContext; class KeyServer; struct AuthCapsInfo; +enum class global_id_status_t { + NONE, + // fresh client (global_id == 0); waiting for CephXAuthenticate + NEW_PENDING, + // connected client; new enough to correctly reclaim global_id + NEW_OK, + // connected client; unknown whether it can reclaim global_id correctly + NEW_NOT_EXPOSED, + // reconnecting client (global_id != 0); waiting for CephXAuthenticate + RECLAIM_PENDING, + // reconnected client; correctly reclaimed global_id + RECLAIM_OK, + // reconnected client; did not properly prove prior global_id ownership + RECLAIM_INSECURE +}; + +std::ostream& operator<<(std::ostream& os, + global_id_status_t global_id_status); + struct AuthServiceHandler { protected: CephContext *cct; -public: EntityName entity_name; - uint64_t global_id; + uint64_t global_id = 0; + global_id_status_t global_id_status = global_id_status_t::NONE; - explicit AuthServiceHandler(CephContext *cct_) : cct(cct_), global_id(0) {} +public: + explicit AuthServiceHandler(CephContext *cct_) : cct(cct_) {} virtual ~AuthServiceHandler() { } - virtual int start_session(EntityName& name, bufferlist::iterator& indata, bufferlist& result, AuthCapsInfo& caps) = 0; - virtual int handle_request(bufferlist::iterator& indata, bufferlist& result, uint64_t& global_id, AuthCapsInfo& caps, uint64_t *auid = NULL) = 0; + int start_session(const EntityName& name, + uint64_t global_id, + bool is_new_global_id, + bufferlist& result, + AuthCapsInfo& caps); + virtual int handle_request(bufferlist::iterator& indata, + bufferlist& result, + AuthCapsInfo& caps, + uint64_t *auid = NULL) = 0; + const EntityName& get_entity_name() { return entity_name; } + uint64_t get_global_id() { return global_id; } + global_id_status_t get_global_id_status() { return global_id_status; } - EntityName& get_entity_name() { return entity_name; } +private: + virtual int do_start_session(bool is_new_global_id, + bufferlist& result, + AuthCapsInfo& caps) = 0; }; extern AuthServiceHandler *get_auth_service_handler(int type, CephContext *cct, KeyServer *ks); diff --git a/src/auth/AuthSessionHandler.cc b/src/auth/AuthSessionHandler.cc index ab46b60c..76a499c2 100644 --- a/src/auth/AuthSessionHandler.cc +++ b/src/auth/AuthSessionHandler.cc @@ -30,6 +30,10 @@ AuthSessionHandler *get_auth_session_handler(CephContext *cct, int protocol, Cry switch (protocol) { case CEPH_AUTH_CEPHX: + + if (key.get_type() == CEPH_CRYPTO_NONE) { + return nullptr; + } return new CephxSessionHandler(cct, key, features); case CEPH_AUTH_NONE: return new AuthNoneSessionHandler(cct, key); diff --git a/src/auth/Crypto.cc b/src/auth/Crypto.cc index 0186b7b2..150052bf 100644 --- a/src/auth/Crypto.cc +++ b/src/auth/Crypto.cc @@ -12,6 +12,9 @@ */ #include +#include +#include + #include "Crypto.h" #ifdef USE_CRYPTOPP # include @@ -37,7 +40,7 @@ int get_random_bytes(char *buf, int len) { - int fd = TEMP_FAILURE_RETRY(::open("/dev/urandom", O_RDONLY)); + int fd = TEMP_FAILURE_RETRY(::open("/dev/urandom", O_RDONLY|O_CLOEXEC)); if (fd < 0) return -errno; int ret = safe_read_exact(fd, buf, len); diff --git a/src/auth/cephx/CephxClientHandler.cc b/src/auth/cephx/CephxClientHandler.cc index 89d42903..5b97917d 100644 --- a/src/auth/cephx/CephxClientHandler.cc +++ b/src/auth/cephx/CephxClientHandler.cc @@ -31,8 +31,6 @@ int CephxClientHandler::build_request(bufferlist& bl) const { ldout(cct, 10) << "build_request" << dendl; - RWLock::RLocker l(lock); - if (need & CEPH_ENTITY_TYPE_AUTH) { /* authenticate */ CephXRequestHeader header; @@ -109,7 +107,6 @@ bool CephxClientHandler::_need_tickets() const int CephxClientHandler::handle_response(int ret, bufferlist::iterator& indata) { ldout(cct, 10) << "handle_response ret = " << ret << dendl; - RWLock::WLocker l(lock); if (ret < 0) return ret; // hrm! @@ -203,7 +200,6 @@ int CephxClientHandler::handle_response(int ret, bufferlist::iterator& indata) AuthAuthorizer *CephxClientHandler::build_authorizer(uint32_t service_id) const { - RWLock::RLocker l(lock); ldout(cct, 10) << "build_authorizer for service " << ceph_entity_type_name(service_id) << dendl; return tickets.build_authorizer(service_id); } @@ -220,7 +216,6 @@ bool CephxClientHandler::build_rotating_request(bufferlist& bl) const void CephxClientHandler::prepare_build_request() { - RWLock::WLocker l(lock); ldout(cct, 10) << "validate_tickets: want=" << want << " need=" << need << " have=" << have << dendl; validate_tickets(); @@ -238,7 +233,6 @@ void CephxClientHandler::validate_tickets() bool CephxClientHandler::need_tickets() { - RWLock::WLocker l(lock); validate_tickets(); ldout(cct, 20) << "need_tickets: want=" << want diff --git a/src/auth/cephx/CephxClientHandler.h b/src/auth/cephx/CephxClientHandler.h index 9c652224..a6539b97 100644 --- a/src/auth/cephx/CephxClientHandler.h +++ b/src/auth/cephx/CephxClientHandler.h @@ -47,8 +47,11 @@ public: reset(); } + CephxClientHandler* clone() const override { + return new CephxClientHandler(*this); + } + void reset() override { - RWLock::WLocker l(lock); starting = true; server_challenge = 0; } @@ -64,7 +67,6 @@ public: bool need_tickets() override; void set_global_id(uint64_t id) override { - RWLock::WLocker l(lock); global_id = id; tickets.global_id = id; } diff --git a/src/auth/cephx/CephxKeyServer.cc b/src/auth/cephx/CephxKeyServer.cc index e06de660..5f530271 100644 --- a/src/auth/cephx/CephxKeyServer.cc +++ b/src/auth/cephx/CephxKeyServer.cc @@ -22,7 +22,8 @@ #define dout_prefix *_dout << "cephx keyserverdata: " bool KeyServerData::get_service_secret(CephContext *cct, uint32_t service_id, - ExpiringCryptoKey& secret, uint64_t& secret_id) const + CryptoKey& secret, uint64_t& secret_id, + double& ttl) const { map::const_iterator iter = rotating_secrets.find(service_id); @@ -39,25 +40,25 @@ bool KeyServerData::get_service_secret(CephContext *cct, uint32_t service_id, if (secrets.secrets.size() > 1) ++riter; - if (riter->second.expiration < ceph_clock_now()) + utime_t now = ceph_clock_now(); + if (riter->second.expiration < now) ++riter; // "current" key has expired, use "next" key instead secret_id = riter->first; - secret = riter->second; - ldout(cct, 30) << "get_service_secret service " << ceph_entity_type_name(service_id) - << " id " << secret_id << " " << secret << dendl; - return true; -} - -bool KeyServerData::get_service_secret(CephContext *cct, uint32_t service_id, - CryptoKey& secret, uint64_t& secret_id) const -{ - ExpiringCryptoKey e; - - if (!get_service_secret(cct, service_id, e, secret_id)) - return false; + secret = riter->second.key; - secret = e.key; + // ttl may have just been increased by the user + // cap it by expiration of "next" key to prevent handing out a ticket + // with a bogus, possibly way into the future, validity + ttl = service_id == CEPH_ENTITY_TYPE_AUTH ? + cct->_conf->auth_mon_ticket_ttl : cct->_conf->auth_service_ticket_ttl; + ttl = min(ttl, static_cast( + secrets.secrets.rbegin()->second.expiration - now)); + + ldout(cct, 30) << __func__ << " service " + << ceph_entity_type_name(service_id) << " secret_id " + << secret_id << " " << riter->second << " ttl " << ttl + << dendl; return true; } @@ -233,20 +234,12 @@ bool KeyServer::get_caps(const EntityName& name, const string& type, return data.get_caps(cct, name, type, caps_info); } -bool KeyServer::get_service_secret(uint32_t service_id, - ExpiringCryptoKey& secret, uint64_t& secret_id) const +bool KeyServer::get_service_secret(uint32_t service_id, CryptoKey& secret, + uint64_t& secret_id, double& ttl) const { Mutex::Locker l(lock); - return data.get_service_secret(cct, service_id, secret, secret_id); -} - -bool KeyServer::get_service_secret(uint32_t service_id, - CryptoKey& secret, uint64_t& secret_id) const -{ - Mutex::Locker l(lock); - - return data.get_service_secret(cct, service_id, secret, secret_id); + return data.get_service_secret(cct, service_id, secret, secret_id, ttl); } bool KeyServer::get_service_secret(uint32_t service_id, @@ -421,12 +414,15 @@ bool KeyServer::get_service_caps(const EntityName& name, uint32_t service_id, } -int KeyServer::_build_session_auth_info(uint32_t service_id, CephXServiceTicketInfo& auth_ticket_info, - CephXSessionAuthInfo& info) +int KeyServer::_build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + CephXSessionAuthInfo& info, + double ttl) { info.service_id = service_id; - info.ticket = auth_ticket_info.ticket; - info.ticket.init_timestamps(ceph_clock_now(), cct->_conf->auth_service_ticket_ttl); + info.ticket = parent_ticket; + info.ticket.init_timestamps(ceph_clock_now(), ttl); + info.validity.set_from_double(ttl); generate_secret(info.session_key); @@ -440,25 +436,31 @@ int KeyServer::_build_session_auth_info(uint32_t service_id, CephXServiceTicketI return 0; } -int KeyServer::build_session_auth_info(uint32_t service_id, CephXServiceTicketInfo& auth_ticket_info, +int KeyServer::build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, CephXSessionAuthInfo& info) { - if (!get_service_secret(service_id, info.service_secret, info.secret_id)) { + double ttl; + if (!get_service_secret(service_id, info.service_secret, info.secret_id, + ttl)) { return -EPERM; } Mutex::Locker l(lock); - - return _build_session_auth_info(service_id, auth_ticket_info, info); + return _build_session_auth_info(service_id, parent_ticket, info, ttl); } -int KeyServer::build_session_auth_info(uint32_t service_id, CephXServiceTicketInfo& auth_ticket_info, CephXSessionAuthInfo& info, - CryptoKey& service_secret, uint64_t secret_id) +int KeyServer::build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + const CryptoKey& service_secret, + uint64_t secret_id, + CephXSessionAuthInfo& info) { info.service_secret = service_secret; info.secret_id = secret_id; Mutex::Locker l(lock); - return _build_session_auth_info(service_id, auth_ticket_info, info); + return _build_session_auth_info(service_id, parent_ticket, info, + cct->_conf->auth_service_ticket_ttl); } diff --git a/src/auth/cephx/CephxKeyServer.h b/src/auth/cephx/CephxKeyServer.h index ff91e96d..7da08b1b 100644 --- a/src/auth/cephx/CephxKeyServer.h +++ b/src/auth/cephx/CephxKeyServer.h @@ -89,9 +89,8 @@ struct KeyServerData { } bool get_service_secret(CephContext *cct, uint32_t service_id, - ExpiringCryptoKey& secret, uint64_t& secret_id) const; - bool get_service_secret(CephContext *cct, uint32_t service_id, - CryptoKey& secret, uint64_t& secret_id) const; + CryptoKey& secret, uint64_t& secret_id, + double& ttl) const; bool get_service_secret(CephContext *cct, uint32_t service_id, uint64_t secret_id, CryptoKey& secret) const; bool get_auth(const EntityName& name, EntityAuth& auth) const; @@ -193,7 +192,9 @@ class KeyServer : public KeyStore { bool _check_rotating_secrets(); void _dump_rotating_secrets(); int _build_session_auth_info(uint32_t service_id, - CephXServiceTicketInfo& auth_ticket_info, CephXSessionAuthInfo& info); + const AuthTicket& parent_ticket, + CephXSessionAuthInfo& info, + double ttl); bool _get_service_caps(const EntityName& name, uint32_t service_id, AuthCapsInfo& caps) const; public: @@ -207,15 +208,18 @@ public: int start_server(); void rotate_timeout(double timeout); - int build_session_auth_info(uint32_t service_id, CephXServiceTicketInfo& auth_ticket_info, CephXSessionAuthInfo& info); - int build_session_auth_info(uint32_t service_id, CephXServiceTicketInfo& auth_ticket_info, CephXSessionAuthInfo& info, - CryptoKey& service_secret, uint64_t secret_id); + int build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + CephXSessionAuthInfo& info); + int build_session_auth_info(uint32_t service_id, + const AuthTicket& parent_ticket, + const CryptoKey& service_secret, + uint64_t secret_id, + CephXSessionAuthInfo& info); /* get current secret for specific service type */ - bool get_service_secret(uint32_t service_id, ExpiringCryptoKey& service_key, - uint64_t& secret_id) const; - bool get_service_secret(uint32_t service_id, CryptoKey& service_key, - uint64_t& secret_id) const; + bool get_service_secret(uint32_t service_id, CryptoKey& secret, + uint64_t& secret_id, double& ttl) const; bool get_service_secret(uint32_t service_id, uint64_t secret_id, CryptoKey& secret) const override; diff --git a/src/auth/cephx/CephxProtocol.cc b/src/auth/cephx/CephxProtocol.cc index cc5f4496..2905c3c9 100644 --- a/src/auth/cephx/CephxProtocol.cc +++ b/src/auth/cephx/CephxProtocol.cc @@ -349,8 +349,10 @@ void CephXTicketManager::validate_tickets(uint32_t mask, uint32_t& have, uint32_ << " need " << need << dendl; } -bool cephx_decode_ticket(CephContext *cct, KeyStore *keys, uint32_t service_id, - CephXTicketBlob& ticket_blob, CephXServiceTicketInfo& ticket_info) +bool cephx_decode_ticket(CephContext *cct, KeyStore *keys, + uint32_t service_id, + const CephXTicketBlob& ticket_blob, + CephXServiceTicketInfo& ticket_info) { uint64_t secret_id = ticket_blob.secret_id; CryptoKey service_secret; diff --git a/src/auth/cephx/CephxProtocol.h b/src/auth/cephx/CephxProtocol.h index b5ec897f..cd20cd43 100644 --- a/src/auth/cephx/CephxProtocol.h +++ b/src/auth/cephx/CephxProtocol.h @@ -168,12 +168,16 @@ struct CephXAuthenticate { uint64_t key; CephXTicketBlob old_ticket; + bool old_ticket_may_be_omitted; + void encode(bufferlist& bl) const { - __u8 struct_v = 1; + __u8 struct_v = 3; ::encode(struct_v, bl); ::encode(client_challenge, bl); ::encode(key, bl); ::encode(old_ticket, bl); + uint32_t other_keys = 0; + ::encode(other_keys, bl); } void decode(bufferlist::iterator& bl) { __u8 struct_v; @@ -181,6 +185,13 @@ struct CephXAuthenticate { ::decode(client_challenge, bl); ::decode(key, bl); ::decode(old_ticket, bl); + + // v2 and v3 encodings are the same, but: + // - some clients that send v1 or v2 don't populate old_ticket + // on reconnects (but do on renewals) + // - any client that sends v3 or later is expected to populate + // old_ticket both on reconnects and renewals + old_ticket_may_be_omitted = struct_v < 3; } }; WRITE_CLASS_ENCODER(CephXAuthenticate) @@ -429,7 +440,8 @@ WRITE_CLASS_ENCODER(CephXAuthorize) * Decode an extract ticket */ bool cephx_decode_ticket(CephContext *cct, KeyStore *keys, - uint32_t service_id, CephXTicketBlob& ticket_blob, + uint32_t service_id, + const CephXTicketBlob& ticket_blob, CephXServiceTicketInfo& ticket_info); /* @@ -453,8 +465,8 @@ extern bool cephx_verify_authorizer( static constexpr uint64_t AUTH_ENC_MAGIC = 0xff009cad8826aa55ull; template -void decode_decrypt_enc_bl(CephContext *cct, T& t, CryptoKey key, bufferlist& bl_enc, - std::string &error) +void decode_decrypt_enc_bl(CephContext *cct, T& t, CryptoKey key, + const bufferlist& bl_enc, std::string &error) { uint64_t magic; bufferlist bl; diff --git a/src/auth/cephx/CephxServiceHandler.cc b/src/auth/cephx/CephxServiceHandler.cc index b06e0080..0a6c3431 100644 --- a/src/auth/cephx/CephxServiceHandler.cc +++ b/src/auth/cephx/CephxServiceHandler.cc @@ -26,9 +26,12 @@ #undef dout_prefix #define dout_prefix *_dout << "cephx server " << entity_name << ": " -int CephxServiceHandler::start_session(EntityName& name, bufferlist::iterator& indata, bufferlist& result_bl, AuthCapsInfo& caps) +int CephxServiceHandler::do_start_session(bool is_new_global_id, + bufferlist& result_bl, + AuthCapsInfo& caps) { - entity_name = name; + global_id_status = is_new_global_id ? global_id_status_t::NEW_PENDING : + global_id_status_t::RECLAIM_PENDING; get_random_bytes((char *)&server_challenge, sizeof(server_challenge)); if (!server_challenge) @@ -41,7 +44,89 @@ int CephxServiceHandler::start_session(EntityName& name, bufferlist::iterator& i return CEPH_AUTH_CEPHX; } -int CephxServiceHandler::handle_request(bufferlist::iterator& indata, bufferlist& result_bl, uint64_t& global_id, AuthCapsInfo& caps, uint64_t *auid) +int CephxServiceHandler::verify_old_ticket(const CephXAuthenticate& req, + CephXServiceTicketInfo& old_ticket_info, + bool& should_enc_ticket) +{ + ldout(cct, 20) << " checking old_ticket: secret_id=" + << req.old_ticket.secret_id + << " len=" << req.old_ticket.blob.length() + << ", old_ticket_may_be_omitted=" + << req.old_ticket_may_be_omitted << dendl; + ceph_assert(global_id_status != global_id_status_t::NONE); + if (global_id_status == global_id_status_t::NEW_PENDING) { + // old ticket is not needed + if (req.old_ticket.blob.length()) { + ldout(cct, 0) << " superfluous ticket presented" << dendl; + return -EINVAL; + } + if (req.old_ticket_may_be_omitted) { + ldout(cct, 10) << " new global_id " << global_id + << " (unexposed legacy client)" << dendl; + global_id_status = global_id_status_t::NEW_NOT_EXPOSED; + } else { + ldout(cct, 10) << " new global_id " << global_id << dendl; + global_id_status = global_id_status_t::NEW_OK; + } + return 0; + } + + if (!req.old_ticket.blob.length()) { + // old ticket is needed but not presented + if (cct->_conf->auth_allow_insecure_global_id_reclaim && + req.old_ticket_may_be_omitted) { + ldout(cct, 10) << " allowing reclaim of global_id " << global_id + << " with no ticket presented (legacy client, auth_allow_insecure_global_id_reclaim=true)" + << dendl; + global_id_status = global_id_status_t::RECLAIM_INSECURE; + return 0; + } + ldout(cct, 0) << " attempt to reclaim global_id " << global_id + << " without presenting ticket" << dendl; + return -EACCES; + } + + if (!cephx_decode_ticket(cct, key_server, CEPH_ENTITY_TYPE_AUTH, + req.old_ticket, old_ticket_info)) { + if (cct->_conf->auth_allow_insecure_global_id_reclaim && + req.old_ticket_may_be_omitted) { + ldout(cct, 10) << " allowing reclaim of global_id " << global_id + << " using bad ticket (legacy client, auth_allow_insecure_global_id_reclaim=true)" + << dendl; + global_id_status = global_id_status_t::RECLAIM_INSECURE; + return 0; + } + ldout(cct, 0) << " attempt to reclaim global_id " << global_id + << " using bad ticket" << dendl; + return -EACCES; + } + ldout(cct, 20) << " decoded old_ticket: global_id=" + << old_ticket_info.ticket.global_id << dendl; + if (global_id != old_ticket_info.ticket.global_id) { + if (cct->_conf->auth_allow_insecure_global_id_reclaim && + req.old_ticket_may_be_omitted) { + ldout(cct, 10) << " allowing reclaim of global_id " << global_id + << " using mismatching ticket (legacy client, auth_allow_insecure_global_id_reclaim=true)" + << dendl; + global_id_status = global_id_status_t::RECLAIM_INSECURE; + return 0; + } + ldout(cct, 0) << " attempt to reclaim global_id " << global_id + << " using mismatching ticket" << dendl; + return -EACCES; + } + ldout(cct, 10) << " allowing reclaim of global_id " << global_id + << " (valid ticket presented, will encrypt new ticket)" + << dendl; + global_id_status = global_id_status_t::RECLAIM_OK; + should_enc_ticket = true; + return 0; +} + +int CephxServiceHandler::handle_request(bufferlist::iterator& indata, + bufferlist& result_bl, + AuthCapsInfo& caps, + uint64_t *auid) { int ret = 0; @@ -60,13 +145,13 @@ int CephxServiceHandler::handle_request(bufferlist::iterator& indata, bufferlist CryptoKey secret; if (!key_server->get_secret(entity_name, secret)) { ldout(cct, 0) << "couldn't find entity name: " << entity_name << dendl; - ret = -EPERM; - break; + ret = -EPERM; + break; } if (!server_challenge) { - ret = -EPERM; - break; + ret = -EPERM; + break; } uint64_t expected_key; @@ -74,18 +159,18 @@ int CephxServiceHandler::handle_request(bufferlist::iterator& indata, bufferlist cephx_calc_client_server_challenge(cct, secret, server_challenge, req.client_challenge, &expected_key, error); if (!error.empty()) { - ldout(cct, 0) << " cephx_calc_client_server_challenge error: " << error << dendl; - ret = -EPERM; - break; + ldout(cct, 0) << " cephx_calc_client_server_challenge error: " << error << dendl; + ret = -EPERM; + break; } ldout(cct, 20) << " checking key: req.key=" << hex << req.key << " expected_key=" << expected_key << dec << dendl; if (req.key != expected_key) { ldout(cct, 0) << " unexpected key: req.key=" << hex << req.key - << " expected_key=" << expected_key << dec << dendl; + << " expected_key=" << expected_key << dec << dendl; ret = -EPERM; - break; + break; } CryptoKey session_key; @@ -94,35 +179,38 @@ int CephxServiceHandler::handle_request(bufferlist::iterator& indata, bufferlist EntityAuth eauth; if (! key_server->get_auth(entity_name, eauth)) { - ret = -EPERM; - break; + ret = -EPERM; + break; } + CephXServiceTicketInfo old_ticket_info; + ret = verify_old_ticket(req, old_ticket_info, should_enc_ticket); + if (ret) { + ldout(cct, 0) << " could not verify old ticket" << dendl; + break; + } - if (cephx_decode_ticket(cct, key_server, CEPH_ENTITY_TYPE_AUTH, - req.old_ticket, old_ticket_info)) { - global_id = old_ticket_info.ticket.global_id; - ldout(cct, 10) << "decoded old_ticket with global_id=" << global_id << dendl; - should_enc_ticket = true; + double ttl; + if (!key_server->get_service_secret(CEPH_ENTITY_TYPE_AUTH, + info.service_secret, info.secret_id, + ttl)) { + ldout(cct, 0) << " could not get service secret for auth subsystem" << dendl; + ret = -EIO; + break; } - info.ticket.init_timestamps(ceph_clock_now(), cct->_conf->auth_mon_ticket_ttl); + info.service_id = CEPH_ENTITY_TYPE_AUTH; info.ticket.name = entity_name; info.ticket.global_id = global_id; info.ticket.auid = eauth.auid; - info.validity += cct->_conf->auth_mon_ticket_ttl; + info.ticket.init_timestamps(ceph_clock_now(), ttl); + info.validity.set_from_double(ttl); if (auid) *auid = eauth.auid; key_server->generate_secret(session_key); info.session_key = session_key; - info.service_id = CEPH_ENTITY_TYPE_AUTH; - if (!key_server->get_service_secret(CEPH_ENTITY_TYPE_AUTH, info.service_secret, info.secret_id)) { - ldout(cct, 0) << " could not get service secret for auth subsystem" << dendl; - ret = -EIO; - break; - } vector info_vec; info_vec.push_back(info); @@ -130,7 +218,7 @@ int CephxServiceHandler::handle_request(bufferlist::iterator& indata, bufferlist build_cephx_response_header(cephx_header.request_type, 0, result_bl); if (!cephx_build_service_ticket_reply(cct, eauth.key, info_vec, should_enc_ticket, old_ticket_info.session_key, result_bl)) { - ret = -EIO; + ret = -EIO; } if (!key_server->get_service_caps(entity_name, CEPH_ENTITY_TYPE_MON, caps)) { @@ -156,7 +244,7 @@ int CephxServiceHandler::handle_request(bufferlist::iterator& indata, bufferlist if (!cephx_verify_authorizer(cct, key_server, indata, auth_ticket_info, nullptr, tmp_bl)) { ret = -EPERM; - break; + break; } CephXServiceTicketRequest ticket_req; @@ -168,13 +256,18 @@ int CephxServiceHandler::handle_request(bufferlist::iterator& indata, bufferlist int found_services = 0; int service_err = 0; for (uint32_t service_id = 1; service_id <= ticket_req.keys; - service_id <<= 1) { - if (ticket_req.keys & service_id) { - ldout(cct, 10) << " adding key for service " + service_id <<= 1) { + // skip CEPH_ENTITY_TYPE_AUTH: auth ticket must be obtained with + // CEPHX_GET_AUTH_SESSION_KEY + if ((ticket_req.keys & service_id) && + service_id != CEPH_ENTITY_TYPE_AUTH) { + ldout(cct, 10) << " adding key for service " << ceph_entity_type_name(service_id) << dendl; CephXSessionAuthInfo info; - int r = key_server->build_session_auth_info(service_id, - auth_ticket_info, info); + int r = key_server->build_session_auth_info( + service_id, + auth_ticket_info.ticket, + info); // tolerate missing MGR rotating key for the purposes of upgrades. if (r < 0) { ldout(cct, 10) << " missing key for service " @@ -182,9 +275,8 @@ int CephxServiceHandler::handle_request(bufferlist::iterator& indata, bufferlist service_err = r; continue; } - info.validity += cct->_conf->auth_service_ticket_ttl; - info_vec.push_back(info); - ++found_services; + info_vec.push_back(info); + ++found_services; } } if (!found_services && service_err) { diff --git a/src/auth/cephx/CephxServiceHandler.h b/src/auth/cephx/CephxServiceHandler.h index 390a6dc1..18f87e39 100644 --- a/src/auth/cephx/CephxServiceHandler.h +++ b/src/auth/cephx/CephxServiceHandler.h @@ -19,6 +19,8 @@ #include "auth/Auth.h" class KeyServer; +struct CephXAuthenticate; +struct CephXServiceTicketInfo; class CephxServiceHandler : public AuthServiceHandler { KeyServer *key_server; @@ -29,9 +31,21 @@ public: : AuthServiceHandler(cct_), key_server(ks), server_challenge(0) {} ~CephxServiceHandler() override {} - int start_session(EntityName& name, bufferlist::iterator& indata, bufferlist& result_bl, AuthCapsInfo& caps) override; - int handle_request(bufferlist::iterator& indata, bufferlist& result_bl, uint64_t& global_id, AuthCapsInfo& caps, uint64_t *auid = NULL) override; - void build_cephx_response_header(int request_type, int status, bufferlist& bl); + int handle_request(bufferlist::iterator& indata, + bufferlist& result_bl, + AuthCapsInfo& caps, + uint64_t *auid = NULL) override; + +private: + int do_start_session(bool is_new_global_id, + bufferlist& result_bl, + AuthCapsInfo& caps) override; + + int verify_old_ticket(const CephXAuthenticate& req, + CephXServiceTicketInfo& old_ticket_info, + bool& should_enc_ticket); + void build_cephx_response_header(int request_type, int status, + bufferlist& bl); }; #endif diff --git a/src/auth/none/AuthNoneClientHandler.h b/src/auth/none/AuthNoneClientHandler.h index 369aa548..2a52f331 100644 --- a/src/auth/none/AuthNoneClientHandler.h +++ b/src/auth/none/AuthNoneClientHandler.h @@ -22,9 +22,13 @@ class AuthNoneClientHandler : public AuthClientHandler { public: - AuthNoneClientHandler(CephContext *cct_, RotatingKeyRing *rkeys) + AuthNoneClientHandler(CephContext *cct_) : AuthClientHandler(cct_) {} + AuthNoneClientHandler* clone() const override { + return new AuthNoneClientHandler(*this); + } + void reset() override { } void prepare_build_request() override {} @@ -35,7 +39,6 @@ public: int get_protocol() const override { return CEPH_AUTH_NONE; } AuthAuthorizer *build_authorizer(uint32_t service_id) const override { - RWLock::RLocker l(lock); AuthNoneAuthorizer *auth = new AuthNoneAuthorizer(); if (auth) { auth->build_authorizer(cct->_conf->name, global_id); @@ -46,7 +49,6 @@ public: bool need_tickets() override { return false; } void set_global_id(uint64_t id) override { - RWLock::WLocker l(lock); global_id = id; } private: diff --git a/src/auth/none/AuthNoneServiceHandler.h b/src/auth/none/AuthNoneServiceHandler.h index 120a7a98..17ce2f8a 100644 --- a/src/auth/none/AuthNoneServiceHandler.h +++ b/src/auth/none/AuthNoneServiceHandler.h @@ -26,15 +26,20 @@ public: : AuthServiceHandler(cct_) {} ~AuthNoneServiceHandler() override {} - int start_session(EntityName& name, bufferlist::iterator& indata, bufferlist& result_bl, AuthCapsInfo& caps) override { - entity_name = name; + int handle_request(bufferlist::iterator& indata, + bufferlist& result_bl, + AuthCapsInfo& caps, + uint64_t *auid = NULL) override { + return 0; + } + +private: + int do_start_session(bool is_new_global_id, + bufferlist& result_bl, + AuthCapsInfo& caps) override { caps.allow_all = true; return CEPH_AUTH_NONE; } - int handle_request(bufferlist::iterator& indata, bufferlist& result_bl, uint64_t& global_id, AuthCapsInfo& caps, uint64_t *auid = NULL) override { - return 0; - } - void build_cephx_response_header(int request_type, int status, bufferlist& bl) { } }; #endif diff --git a/src/auth/unknown/AuthUnknownServiceHandler.h b/src/auth/unknown/AuthUnknownServiceHandler.h index 5c1e511e..61ca6b29 100644 --- a/src/auth/unknown/AuthUnknownServiceHandler.h +++ b/src/auth/unknown/AuthUnknownServiceHandler.h @@ -26,14 +26,18 @@ public: : AuthServiceHandler(cct_) {} ~AuthUnknownServiceHandler() {} - int start_session(EntityName& name, bufferlist::iterator& indata, bufferlist& result_bl, AuthCapsInfo& caps) { + int start_session(EntityName& name, + bufferlist& result_bl, + AuthCapsInfo& caps) override { return CEPH_AUTH_UNKNOWN; } - int handle_request(bufferlist::iterator& indata, bufferlist& result_bl, uint64_t& global_id, AuthCapsInfo& caps, uint64_t *auid = NULL) { + int handle_request(bufferlist::iterator& indata, + bufferlist& result_bl, + AuthCapsInfo& caps, + uint64_t *auid = NULL) { ceph_abort(); // shouldn't get called return 0; } - void build_cephx_response_header(int request_type, int status, bufferlist& bl) { } }; #endif diff --git a/src/common/legacy_config_opts.h b/src/common/legacy_config_opts.h index 38b36a60..cbb4528e 100644 --- a/src/common/legacy_config_opts.h +++ b/src/common/legacy_config_opts.h @@ -340,6 +340,8 @@ OPTION(cephx_service_require_version, OPT_INT) OPTION(cephx_sign_messages, OPT_BOOL) // Default to signing session messages if supported OPTION(auth_mon_ticket_ttl, OPT_DOUBLE) OPTION(auth_service_ticket_ttl, OPT_DOUBLE) +OPTION(auth_allow_insecure_global_id_reclaim, OPT_BOOL) +OPTION(auth_expose_insecure_global_id_reclaim, OPT_BOOL) OPTION(auth_debug, OPT_BOOL) // if true, assert when weird things happen OPTION(mon_client_hunt_parallel, OPT_U32) // how many mons to try to connect to in parallel during hunt OPTION(mon_client_hunt_interval, OPT_DOUBLE) // try new mon every N seconds until we connect diff --git a/src/common/options.cc b/src/common/options.cc index 1ed027c9..cf8f6bbe 100644 --- a/src/common/options.cc +++ b/src/common/options.cc @@ -1128,6 +1128,22 @@ std::vector