ceph/0026-CVE-2021-20288.patch
wangzengliang 10f5ca1c94 Fix CVE-2021-20288
(cherry picked from commit 44f0f912a2e090d084cf3cea50d77f765d6eadbe)
2022-05-26 15:20:19 +08:00

1586 lines
60 KiB
Diff

From f3a4166379b12d4a7bba667fe761e5b660552db1 Mon Sep 17 00:00:00 2001
From: Ilya Dryomov <idryomov@gmail.com>
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 <idryomov@gmail.com>
Reviewed-by: Sage Weil <sage@redhat.com>
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.<id> 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 <<EOF
diff --git a/src/auth/AuthClientHandler.cc b/src/auth/AuthClientHandler.cc
index a76d1e4c..493ea982 100644
--- a/src/auth/AuthClientHandler.cc
+++ b/src/auth/AuthClientHandler.cc
@@ -26,7 +26,7 @@ AuthClientHandler *get_auth_client_handler(CephContext *cct, int proto,
case CEPH_AUTH_CEPHX:
return new CephxClientHandler(cct, rkeys);
case CEPH_AUTH_NONE:
- return new AuthNoneClientHandler(cct, rkeys);
+ return new AuthNoneClientHandler{cct};
default:
return NULL;
}
diff --git a/src/auth/AuthClientHandler.h b/src/auth/AuthClientHandler.h
index b397f883..ecae5e63 100644
--- a/src/auth/AuthClientHandler.h
+++ b/src/auth/AuthClientHandler.h
@@ -17,7 +17,6 @@
#include "auth/Auth.h"
-#include "common/RWLock.h"
class CephContext;
struct MAuthReply;
@@ -31,18 +30,18 @@ protected:
uint32_t want;
uint32_t have;
uint32_t need;
- RWLock lock;
public:
explicit AuthClientHandler(CephContext *cct_)
- : cct(cct_), global_id(0), want(CEPH_ENTITY_TYPE_AUTH), have(0), need(0),
- lock("AuthClientHandler::lock") {}
+ : cct(cct_), global_id(0), want(CEPH_ENTITY_TYPE_AUTH), have(0), need(0)
+ {}
virtual ~AuthClientHandler() {}
+ virtual AuthClientHandler* clone() const = 0;
+
void init(const EntityName& n) { name = n; }
void set_want_keys(__u32 keys) {
- RWLock::WLocker l(lock);
want = keys | CEPH_ENTITY_TYPE_AUTH;
validate_tickets();
}
diff --git a/src/auth/AuthServiceHandler.cc b/src/auth/AuthServiceHandler.cc
index bd265c4f..73848e4e 100644
--- a/src/auth/AuthServiceHandler.cc
+++ b/src/auth/AuthServiceHandler.cc
@@ -15,10 +15,52 @@
#include "AuthServiceHandler.h"
#include "cephx/CephxServiceHandler.h"
#include "none/AuthNoneServiceHandler.h"
+#include "common/dout.h"
#define dout_subsys ceph_subsys_auth
+std::ostream& operator<<(std::ostream& os,
+ global_id_status_t global_id_status)
+{
+ switch (global_id_status) {
+ case global_id_status_t::NONE:
+ return os << "none";
+ case global_id_status_t::NEW_PENDING:
+ return os << "new_pending";
+ case global_id_status_t::NEW_OK:
+ return os << "new_ok";
+ case global_id_status_t::NEW_NOT_EXPOSED:
+ return os << "new_not_exposed";
+ case global_id_status_t::RECLAIM_PENDING:
+ return os << "reclaim_pending";
+ case global_id_status_t::RECLAIM_OK:
+ return os << "reclaim_ok";
+ case global_id_status_t::RECLAIM_INSECURE:
+ return os << "reclaim_insecure";
+ default:
+ ceph_abort();
+ }
+}
+
+int AuthServiceHandler::start_session(const EntityName& entity_name,
+ uint64_t global_id,
+ bool is_new_global_id,
+ bufferlist& result,
+ AuthCapsInfo& caps)
+{
+ ceph_assert(!this->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 <sstream>
+#include <limits>
+#include <fcntl.h>
+
#include "Crypto.h"
#ifdef USE_CRYPTOPP
# include <cryptopp/modes.h>
@@ -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<uint32_t, RotatingSecrets>::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<double>(
+ 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 <typename T>
-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<CephXSessionAuthInfo> 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<Option> get_global_options() {
.set_default(900)
.set_description(""),
+ Option("mon_warn_on_insecure_global_id_reclaim", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
+ .set_default(true)
+ .add_service("mon")
+ .set_description("issue AUTH_INSECURE_GLOBAL_ID_RECLAIM health warning if any connected clients are insecurely reclaiming global_id")
+ .add_see_also("mon_warn_on_insecure_global_id_reclaim_allowed")
+ .add_see_also("auth_allow_insecure_global_id_reclaim")
+ .add_see_also("auth_expose_insecure_global_id_reclaim"),
+
+ Option("mon_warn_on_insecure_global_id_reclaim_allowed", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
+ .set_default(true)
+ .add_service("mon")
+ .set_description("issue AUTH_INSECURE_GLOBAL_ID_RECLAIM_ALLOWED health warning if insecure global_id reclaim is allowed")
+ .add_see_also("mon_warn_on_insecure_global_id_reclaim")
+ .add_see_also("auth_allow_insecure_global_id_reclaim")
+ .add_see_also("auth_expose_insecure_global_id_reclaim"),
+
Option("mon_force_standby_active", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
.set_default(true)
.set_description(""),
@@ -1504,13 +1520,29 @@ std::vector<Option> get_global_options() {
.set_description(""),
Option("auth_mon_ticket_ttl", Option::TYPE_FLOAT, Option::LEVEL_ADVANCED)
- .set_default(12_hr)
+ .set_default(72_hr)
.set_description(""),
Option("auth_service_ticket_ttl", Option::TYPE_FLOAT, Option::LEVEL_ADVANCED)
.set_default(1_hr)
.set_description(""),
+ Option("auth_allow_insecure_global_id_reclaim", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
+ .set_default(true)
+ .set_description("Allow reclaiming global_id without presenting a valid ticket proving previous possession of that global_id")
+ .set_long_description("Allowing unauthorized global_id (re)use poses a security risk. Unfortunately, older clients may omit their ticket on reconnects and therefore rely on this being allowed for preserving their global_id for the lifetime of the client instance. Setting this value to false would immediately prevent new connections from those clients (assuming auth_expose_insecure_global_id_reclaim set to true) and eventually break existing sessions as well (regardless of auth_expose_insecure_global_id_reclaim setting).")
+ .add_see_also("mon_warn_on_insecure_global_id_reclaim")
+ .add_see_also("mon_warn_on_insecure_global_id_reclaim_allowed")
+ .add_see_also("auth_expose_insecure_global_id_reclaim"),
+
+ Option("auth_expose_insecure_global_id_reclaim", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
+ .set_default(true)
+ .set_description("Force older clients that may omit their ticket on reconnects to reconnect as part of establishing a session")
+ .set_long_description("In permissive mode (auth_allow_insecure_global_id_reclaim set to true), this helps with identifying clients that are not patched. In enforcing mode (auth_allow_insecure_global_id_reclaim set to false), this is a fail-fast mechanism: don't establish a session that will almost inevitably be broken later.")
+ .add_see_also("mon_warn_on_insecure_global_id_reclaim")
+ .add_see_also("mon_warn_on_insecure_global_id_reclaim_allowed")
+ .add_see_also("auth_allow_insecure_global_id_reclaim"),
+
Option("auth_debug", Option::TYPE_BOOL, Option::LEVEL_DEV)
.set_default(false)
.set_description(""),
diff --git a/src/mon/AuthMonitor.cc b/src/mon/AuthMonitor.cc
index 965958b1..5498f548 100644
--- a/src/mon/AuthMonitor.cc
+++ b/src/mon/AuthMonitor.cc
@@ -375,6 +375,7 @@ bool AuthMonitor::prep_auth(MonOpRequestRef op, bool paxos_writable)
__u32 proto = m->protocol;
bool start = false;
EntityName entity_name;
+ bool is_new_global_id = false;
// set up handler?
if (m->protocol == 0 && !s->auth_handler) {
@@ -493,6 +494,7 @@ bool AuthMonitor::prep_auth(MonOpRequestRef op, bool paxos_writable)
assert(!paxos_writable);
return false;
}
+ is_new_global_id = true;
}
try {
@@ -504,13 +506,18 @@ bool AuthMonitor::prep_auth(MonOpRequestRef op, bool paxos_writable)
if (m->monmap_epoch < mon->monmap->get_epoch())
mon->send_latest_monmap(m->get_connection().get());
- proto = s->auth_handler->start_session(entity_name, indata, response_bl, caps_info);
+ proto = s->auth_handler->start_session(entity_name, s->global_id,
+ is_new_global_id, response_bl,
+ caps_info);
ret = 0;
- if (caps_info.allow_all)
+ if (caps_info.allow_all) {
s->caps.set_allow_all();
+ s->authenticated = true;
+ }
} else {
// request
- ret = s->auth_handler->handle_request(indata, response_bl, s->global_id, caps_info, &auid);
+ ret = s->auth_handler->handle_request(indata, response_bl, caps_info,
+ &auid);
}
if (ret == -EIO) {
wait_for_active(op, new C_RetryMessage(this,op));
@@ -527,6 +534,7 @@ bool AuthMonitor::prep_auth(MonOpRequestRef op, bool paxos_writable)
}
s->caps.parse(str, NULL);
s->auid = auid;
+ s->authenticated = true;
}
} catch (const buffer::error &err) {
ret = -EINVAL;
diff --git a/src/mon/HealthMonitor.cc b/src/mon/HealthMonitor.cc
index 883cc056..25437f39 100644
--- a/src/mon/HealthMonitor.cc
+++ b/src/mon/HealthMonitor.cc
@@ -213,6 +213,7 @@ bool HealthMonitor::check_member_health()
{
dout(20) << __func__ << dendl;
bool changed = false;
+ const auto max = g_conf->get_val<uint64_t>("mon_health_max_detail");
// snapshot of usage
DataStats stats;
@@ -281,6 +282,43 @@ bool HealthMonitor::check_member_health()
d.detail.push_back(ds.str());
}
}
+
+ // AUTH_INSECURE_GLOBAL_ID_RECLAIM
+ if (g_conf->get_val<bool>("mon_warn_on_insecure_global_id_reclaim") &&
+ g_conf->get_val<bool>("auth_allow_insecure_global_id_reclaim")) {
+ // Warn if there are any clients that are insecurely renewing their global_id
+ Mutex::Locker l(mon->session_map_lock);
+ list<std::string> detail;
+ for (auto p = mon->session_map.sessions.begin();
+ p != mon->session_map.sessions.end();
+ ++p) {
+ if ((*p)->global_id_status == global_id_status_t::RECLAIM_INSECURE) {
+ ostringstream ds;
+ ds << (*p)->entity_name << " at " << (*p)->inst.addr
+ << " is using insecure global_id reclaim";
+ detail.push_back(ds.str());
+ if (detail.size() >= max) {
+ detail.push_back("...");
+ break;
+ }
+ }
+ }
+ if (!detail.empty()) {
+ ostringstream ss;
+ ss << "client%plurals% %isorare% using insecure global_id reclaim";
+ auto& d = next.add("AUTH_INSECURE_GLOBAL_ID_RECLAIM", HEALTH_WARN, ss.str());
+ d.detail.swap(detail);
+ }
+ }
+ // AUTH_INSECURE_GLOBAL_ID_RECLAIM_ALLOWED
+ if (g_conf->get_val<bool>("mon_warn_on_insecure_global_id_reclaim_allowed") &&
+ g_conf->get_val<bool>("auth_allow_insecure_global_id_reclaim")) {
+ ostringstream ss, ds;
+ ss << "mon%plurals% %isorare% allowing insecure global_id reclaim";
+ auto& d = next.add("AUTH_INSECURE_GLOBAL_ID_RECLAIM_ALLOWED", HEALTH_WARN, ss.str());
+ ds << "mon." << mon->name << " has auth_allow_insecure_global_id_reclaim set to true";
+ d.detail.push_back(ds.str());
+ }
auto p = quorum_checks.find(mon->rank);
if (p == quorum_checks.end()) {
diff --git a/src/mon/MonClient.cc b/src/mon/MonClient.cc
index 952c61a8..2d70f28f 100644
--- a/src/mon/MonClient.cc
+++ b/src/mon/MonClient.cc
@@ -117,7 +117,7 @@ int MonClient::get_monmap_privately()
std::uniform_int_distribution<unsigned> ranks(0, monmap.size() - 1);
while (monmap.fsid.is_zero()) {
auto rank = ranks(rng);
- auto& pending_con = _add_conn(rank, 0);
+ auto& pending_con = _add_conn(rank);
auto con = pending_con.get_con();
ldout(cct, 10) << "querying mon." << monmap.get_name(rank) << " "
<< con->get_peer_addr() << dendl;
@@ -429,7 +429,11 @@ void MonClient::shutdown()
active_con.reset();
pending_cons.clear();
+
auth.reset();
+ global_id = 0;
+ authenticate_err = 0;
+ authenticated = false;
monc_lock.Unlock();
@@ -539,7 +543,7 @@ void MonClient::handle_auth(MAuthReply *m)
_resend_mon_commands();
send_log(true);
if (active_con) {
- std::swap(auth, active_con->get_auth());
+ auth = std::move(active_con->get_auth());
global_id = active_con->get_global_id();
}
}
@@ -596,9 +600,9 @@ void MonClient::_reopen_session(int rank)
_start_hunting();
if (rank >= 0) {
- _add_conn(rank, global_id);
+ _add_conn(rank);
} else {
- _add_conns(global_id);
+ _add_conns();
}
// throw out old queued messages
@@ -628,11 +632,14 @@ void MonClient::_reopen_session(int rank)
_renew_subs();
}
-MonConnection& MonClient::_add_conn(unsigned rank, uint64_t global_id)
+MonConnection& MonClient::_add_conn(unsigned rank)
{
auto peer = monmap.get_addr(rank);
auto conn = messenger->get_connection(monmap.get_inst(rank));
MonConnection mc(cct, conn, global_id);
+ if (auth) {
+ mc.get_auth().reset(auth->clone());
+ }
auto inserted = pending_cons.insert(make_pair(peer, move(mc)));
ldout(cct, 10) << "picked mon." << monmap.get_name(rank)
<< " con " << conn
@@ -641,7 +648,7 @@ MonConnection& MonClient::_add_conn(unsigned rank, uint64_t global_id)
return inserted.first->second;
}
-void MonClient::_add_conns(uint64_t global_id)
+void MonClient::_add_conns()
{
uint16_t min_priority = std::numeric_limits<uint16_t>::max();
for (const auto& m : monmap.mon_info) {
@@ -663,7 +670,7 @@ void MonClient::_add_conns(uint64_t global_id)
n = ranks.size();
}
for (unsigned i = 0; i < n; i++) {
- _add_conn(ranks[i], global_id);
+ _add_conn(ranks[i]);
}
}
@@ -1243,12 +1250,15 @@ int MonConnection::_negotiate(MAuthReply *m,
uint32_t want_keys,
RotatingKeyRing* keyring)
{
+ ldout(cct, 10) << __func__ << " protocol " << m->protocol << dendl;
if (auth && (int)m->protocol == auth->get_protocol()) {
// good, negotiation completed
+ ldout(cct, 10) << __func__ << " protocol " << m->protocol << dendl;
auth->reset();
return 0;
}
+ ldout(cct, 10) << __func__ << " creating new auth" << dendl;
auth.reset(get_auth_client_handler(cct, m->protocol, keyring));
if (!auth) {
ldout(cct, 10) << "no handler for protocol " << m->protocol << dendl;
diff --git a/src/mon/MonClient.h b/src/mon/MonClient.h
index 703da88f..933ef2e4 100644
--- a/src/mon/MonClient.h
+++ b/src/mon/MonClient.h
@@ -209,9 +209,9 @@ private:
void _finish_hunting();
void _finish_auth(int auth_err);
void _reopen_session(int rank = -1);
- MonConnection& _add_conn(unsigned rank, uint64_t global_id);
+ MonConnection& _add_conn(unsigned rank);
void _un_backoff();
- void _add_conns(uint64_t global_id);
+ void _add_conns();
void _send_mon_message(Message *m);
public:
diff --git a/src/mon/Monitor.cc b/src/mon/Monitor.cc
index da1fac90..946bbe37 100644
--- a/src/mon/Monitor.cc
+++ b/src/mon/Monitor.cc
@@ -321,7 +321,7 @@ void Monitor::do_admin_command(string command, cmdmap_t& cmdmap, string format,
if (f) {
f->open_array_section("sessions");
for (auto p : session_map.sessions) {
- f->dump_stream("session") << *p;
+ f->dump_object("session", *p);
}
f->close_section();
f->flush(ss);
@@ -889,7 +889,9 @@ void Monitor::shutdown()
state = STATE_SHUTDOWN;
+ lock.Unlock();
g_conf->remove_observer(this);
+ lock.Lock();
if (admin_hook) {
AdminSocket* admin_socket = cct->get_admin_socket();
@@ -929,6 +931,14 @@ void Monitor::shutdown()
remove_all_sessions();
+ log_client.shutdown();
+
+ // unlock before msgr shutdown...
+ lock.Unlock();
+
+ messenger->shutdown(); // last thing! ceph_mon.cc will delete mon.
+ mgr_messenger->shutdown();
+
if (logger) {
cct->get_perfcounters_collection()->remove(logger);
delete logger;
@@ -941,13 +951,6 @@ void Monitor::shutdown()
cluster_logger = NULL;
}
- log_client.shutdown();
-
- // unlock before msgr shutdown...
- lock.Unlock();
-
- messenger->shutdown(); // last thing! ceph_mon.cc will delete mon.
- mgr_messenger->shutdown();
}
void Monitor::wait_for_paxos_write()
@@ -3835,6 +3838,7 @@ void Monitor::handle_forward(MonOpRequestRef op)
c->set_peer_type(m->client.name.type());
c->set_features(m->con_features);
+ s->authenticated = true;
s->caps = m->client_caps;
dout(10) << " caps are " << s->caps << dendl;
s->entity_name = m->entity_name;
@@ -4183,6 +4187,7 @@ void Monitor::_ms_dispatch(Message *m)
dout(5) << __func__ << " setting monitor caps on this connection" << dendl;
if (!s->caps.is_allow_all()) // but no need to repeatedly copy
s->caps = *mon_caps;
+ s->authenticated = true;
}
s->put();
} else {
@@ -4197,8 +4202,13 @@ void Monitor::_ms_dispatch(Message *m)
if (s->auth_handler) {
s->entity_name = s->auth_handler->get_entity_name();
+ ceph_assert(s->global_id == s->auth_handler->get_global_id());
+ s->global_id_status = s->auth_handler->get_global_id_status();
}
- dout(20) << " caps " << s->caps.get_str() << dendl;
+ dout(20) << " entity_name " << s->entity_name
+ << " global_id " << s->global_id
+ << " (" << s->global_id_status
+ << ") caps " << s->caps.get_str() << dendl;
if ((is_synchronizing() ||
(s->global_id == 0 && !exited_quorum.is_zero())) &&
@@ -4259,6 +4269,31 @@ void Monitor::dispatch_op(MonOpRequestRef op)
if (dealt_with)
return;
+ if (s->authenticated) {
+ // global_id_status == NONE: all sessions for auth_none,
+ // mon <-> mon sessions (including proxied sessions) for cephx
+ ceph_assert(s->global_id_status == global_id_status_t::NONE ||
+ s->global_id_status == global_id_status_t::NEW_OK ||
+ s->global_id_status == global_id_status_t::NEW_NOT_EXPOSED ||
+ s->global_id_status == global_id_status_t::RECLAIM_OK ||
+ s->global_id_status == global_id_status_t::RECLAIM_INSECURE);
+
+ if (cct->_conf->auth_expose_insecure_global_id_reclaim &&
+ s->global_id_status == global_id_status_t::NEW_NOT_EXPOSED) {
+ dout(5) << __func__ << " " << op->get_req()->get_source_inst()
+ << " may omit old_ticket on reconnects, discarding "
+ << *op->get_req() << " and forcing reconnect" << dendl;
+ ceph_assert(s->con && !s->proxy_con);
+ s->con->mark_down();
+ {
+ Mutex::Locker l(session_map_lock);
+ remove_session(s);
+ }
+ op->mark_zap();
+ return;
+ }
+ }
+
/* well, maybe the op belongs to a service... */
op->set_type_service();
/* deal with all messages which caps should be checked somewhere else */
@@ -5775,7 +5810,7 @@ int Monitor::write_default_keyring(bufferlist& bl)
os << g_conf->mon_data << "/keyring";
int err = 0;
- int fd = ::open(os.str().c_str(), O_WRONLY|O_CREAT, 0600);
+ int fd = ::open(os.str().c_str(), O_WRONLY|O_CREAT|O_CLOEXEC, 0600);
if (fd < 0) {
err = -errno;
dout(0) << __func__ << " failed to open " << os.str()
@@ -5824,7 +5859,7 @@ bool Monitor::ms_get_authorizer(int service_id, AuthAuthorizer **authorizer,
if (!auth_cluster_required.is_supported_auth(CEPH_AUTH_CEPHX)) {
// auth_none
dout(20) << __func__ << " building auth_none authorizer" << dendl;
- AuthNoneClientHandler handler(g_ceph_context, nullptr);
+ AuthNoneClientHandler handler{g_ceph_context};
handler.set_global_id(0);
*authorizer = handler.build_authorizer(service_id);
return true;
@@ -5857,8 +5892,8 @@ bool Monitor::ms_get_authorizer(int service_id, AuthAuthorizer **authorizer,
return false;
}
- ret = key_server.build_session_auth_info(service_id, auth_ticket_info, info,
- secret, (uint64_t)-1);
+ ret = key_server.build_session_auth_info(
+ service_id, auth_ticket_info.ticket, secret, (uint64_t)-1, info);
if (ret < 0) {
dout(0) << __func__ << " failed to build mon session_auth_info "
<< cpp_strerror(ret) << dendl;
@@ -5866,7 +5901,7 @@ bool Monitor::ms_get_authorizer(int service_id, AuthAuthorizer **authorizer,
}
} else if (service_id == CEPH_ENTITY_TYPE_MGR) {
// mgr
- ret = key_server.build_session_auth_info(service_id, auth_ticket_info, info);
+ ret = key_server.build_session_auth_info(service_id, auth_ticket_info.ticket, info);
if (ret < 0) {
derr << __func__ << " failed to build mgr service session_auth_info "
<< cpp_strerror(ret) << dendl;
diff --git a/src/mon/MonitorDBStore.h b/src/mon/MonitorDBStore.h
index 00e56a9d..5d3d81c0 100644
--- a/src/mon/MonitorDBStore.h
+++ b/src/mon/MonitorDBStore.h
@@ -607,7 +607,7 @@ class MonitorDBStore
if (!g_conf->mon_debug_dump_json) {
dump_fd_binary = ::open(
g_conf->mon_debug_dump_location.c_str(),
- O_CREAT|O_APPEND|O_WRONLY, 0644);
+ O_CREAT|O_APPEND|O_WRONLY|O_CLOEXEC, 0644);
if (dump_fd_binary < 0) {
dump_fd_binary = -errno;
derr << "Could not open log file, got "
diff --git a/src/mon/Session.h b/src/mon/Session.h
index 9b54f962..6e343c64 100644
--- a/src/mon/Session.h
+++ b/src/mon/Session.h
@@ -50,13 +50,16 @@ struct MonSession : public RefCountedObject {
set<uint64_t> routed_request_tids;
MonCap caps;
uint64_t auid;
- uint64_t global_id;
+
+ bool authenticated = false; ///<true id auth handshake is complete
map<string, Subscription*> sub_map;
epoch_t osd_epoch; // the osdmap epoch sent to the mon client
AuthServiceHandler *auth_handler;
EntityName entity_name;
+ uint64_t global_id = 0;
+ global_id_status_t global_id_status = global_id_status_t::NONE;
ConnectionRef proxy_con;
uint64_t proxy_tid;
@@ -68,7 +71,6 @@ struct MonSession : public RefCountedObject {
con_features(0),
inst(i), closed(false), item(this),
auid(0),
- global_id(0),
osd_epoch(0),
auth_handler(NULL),
proxy_con(NULL), proxy_tid(0) {
@@ -95,6 +97,23 @@ struct MonSession : public RefCountedObject {
service, "", args,
mask & MON_CAP_R, mask & MON_CAP_W, mask & MON_CAP_X);
}
+
+ void dump(Formatter *f) const {
+ f->dump_stream("name") << inst.name;
+ f->dump_stream("entity_name") << entity_name;
+ f->dump_object("addr", inst.addr);
+ f->dump_string("con_type", ceph_entity_type_name(con_type));
+ f->dump_unsigned("con_features", con_features);
+ f->dump_stream("con_features_hex") << std::hex << con_features << std::dec;
+ f->dump_string("con_features_release",
+ ceph_release_name(ceph_release_from_features(con_features)));
+ f->dump_bool("open", !closed);
+ f->dump_object("caps", caps);
+ f->dump_bool("authenticated", authenticated);
+ f->dump_unsigned("global_id", global_id);
+ f->dump_stream("global_id_status") << global_id_status;
+ f->dump_unsigned("osd_epoch", osd_epoch);
+ }
};
--
2.30.0