summaryrefslogtreecommitdiff
path: root/src/mongo
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2019-12-04 17:12:10 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-02-12 19:16:45 +0000
commit812c8338f496da3f43174330e37f07f0aad442d3 (patch)
tree80baa88c0eb7aec60fe1d199b27308deae87d49c /src/mongo
parent37d1ef0d02582ac95a2adf835a341e0ead12abb3 (diff)
downloadmongo-812c8338f496da3f43174330e37f07f0aad442d3.tar.gz
SERVER-44858 Implement speculative sasl auth
create mode 100644 jstests/auth/speculative-auth-replset.js create mode 100644 jstests/auth/speculative-sasl-start.js create mode 100644 jstests/ssl/speculative-auth-replset.js create mode 100644 jstests/ssl/speculative-authenticate.js create mode 100644 src/mongo/db/auth/sasl_commands.h create mode 100644 src/mongo/db/s/balancer/core_options_stub.cpp
Diffstat (limited to 'src/mongo')
-rw-r--r--src/mongo/client/SConscript3
-rw-r--r--src/mongo/client/async_client.cpp38
-rw-r--r--src/mongo/client/async_client.h5
-rw-r--r--src/mongo/client/authenticate.cpp130
-rw-r--r--src/mongo/client/authenticate.h35
-rw-r--r--src/mongo/client/dbclient_base.h4
-rw-r--r--src/mongo/client/dbclient_connection.cpp191
-rw-r--r--src/mongo/client/dbclient_connection.h6
-rw-r--r--src/mongo/client/dbclient_rs.cpp4
-rw-r--r--src/mongo/client/mongo_uri.cpp127
-rw-r--r--src/mongo/client/mongo_uri.h7
-rw-r--r--src/mongo/client/mongo_uri_connect.cpp148
-rw-r--r--src/mongo/client/sasl_client_authenticate.h31
-rw-r--r--src/mongo/client/sasl_client_authenticate_impl.cpp36
-rw-r--r--src/mongo/db/auth/SConscript2
-rw-r--r--src/mongo/db/auth/authentication_session.h9
-rw-r--r--src/mongo/db/auth/sasl_commands.cpp74
-rw-r--r--src/mongo/db/auth/sasl_commands.h42
-rw-r--r--src/mongo/db/auth/sasl_options.cpp10
-rw-r--r--src/mongo/db/auth/sasl_options.idl2
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/authentication_commands.cpp22
-rw-r--r--src/mongo/db/commands/authentication_commands.h5
-rw-r--r--src/mongo/db/commands/server_status_servers.cpp15
-rw-r--r--src/mongo/db/repl/SConscript2
-rw-r--r--src/mongo/db/repl/replication_info.cpp27
-rw-r--r--src/mongo/db/s/SConscript1
-rw-r--r--src/mongo/db/s/balancer/core_options_stub.cpp42
-rw-r--r--src/mongo/db/stats/counters.cpp59
-rw-r--r--src/mongo/db/stats/counters.h27
-rw-r--r--src/mongo/executor/connection_pool_tl.cpp37
-rw-r--r--src/mongo/util/net/ssl_manager.cpp10
-rw-r--r--src/mongo/util/net/ssl_manager.h2
33 files changed, 902 insertions, 252 deletions
diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript
index 4d767b99135..6707bfa3494 100644
--- a/src/mongo/client/SConscript
+++ b/src/mongo/client/SConscript
@@ -139,6 +139,9 @@ env.Library(
'$BUILD_DIR/mongo/executor/remote_command',
'sasl_client'
],
+ LIBDEPS_PRIVATE=[
+ 'connection_string',
+ ],
)
env.Library(
diff --git a/src/mongo/client/async_client.cpp b/src/mongo/client/async_client.cpp
index 10155cf5657..cd7a3f78bd9 100644
--- a/src/mongo/client/async_client.cpp
+++ b/src/mongo/client/async_client.cpp
@@ -37,7 +37,9 @@
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/client/authenticate.h"
+#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/config.h"
+#include "mongo/db/auth/sasl_command_constants.h"
#include "mongo/db/commands/test_commands_enabled.h"
#include "mongo/db/server_options.h"
#include "mongo/db/wire_version.h"
@@ -172,6 +174,42 @@ Future<void> AsyncDBClient::authenticateInternal(boost::optional<std::string> me
return auth::authenticateInternalClient(clientName, mechanismHint, _makeAuthRunCommandHook());
}
+Future<bool> AsyncDBClient::completeSpeculativeAuth(std::shared_ptr<SaslClientSession> session,
+ std::string authDB,
+ BSONObj specAuth,
+ auth::SpeculativeAuthType speculativeAuthType) {
+ if (specAuth.isEmpty()) {
+ // No reply could mean failed auth, or old server.
+ // A false reply will result in an explicit auth later.
+ return false;
+ }
+
+ if (speculativeAuthType == auth::SpeculativeAuthType::kNone) {
+ return Status(ErrorCodes::BadValue,
+ str::stream() << "Received unexpected isMaster."
+ << auth::kSpeculativeAuthenticate << " reply");
+ }
+
+ if (speculativeAuthType == auth::SpeculativeAuthType::kAuthenticate) {
+ return specAuth.hasField(saslCommandUserFieldName);
+ }
+
+ invariant(speculativeAuthType == auth::SpeculativeAuthType::kSaslStart);
+ invariant(session);
+
+ return asyncSaslConversation(_makeAuthRunCommandHook(),
+ session,
+ BSON(saslContinueCommandName << 1),
+ specAuth,
+ std::move(authDB),
+ kSaslClientLogLevelDefault)
+ // Swallow failure even if the initial saslStart was okay.
+ // It's possible for our speculative authentication to fail
+ // while explicit auth succeeds if we're in a keyfile rollover state.
+ // The first passphrase can fail, but later ones may be okay.
+ .onCompletion([](Status status) { return status.isOK(); });
+}
+
Future<void> AsyncDBClient::initWireVersion(const std::string& appName,
executor::NetworkConnectionHook* const hook) {
auto requestObj = _buildIsMasterRequest(appName, hook);
diff --git a/src/mongo/client/async_client.h b/src/mongo/client/async_client.h
index 9a66b49eb54..28e0bc2e9da 100644
--- a/src/mongo/client/async_client.h
+++ b/src/mongo/client/async_client.h
@@ -87,6 +87,11 @@ public:
Future<void> authenticateInternal(boost::optional<std::string> mechanismHint);
+ Future<bool> completeSpeculativeAuth(std::shared_ptr<SaslClientSession> session,
+ std::string authDB,
+ BSONObj specAuth,
+ auth::SpeculativeAuthType speculativeAuthtype);
+
Future<void> initWireVersion(const std::string& appName,
executor::NetworkConnectionHook* const hook);
diff --git a/src/mongo/client/authenticate.cpp b/src/mongo/client/authenticate.cpp
index e76f72035a3..71c9aa3cbc6 100644
--- a/src/mongo/client/authenticate.cpp
+++ b/src/mongo/client/authenticate.cpp
@@ -323,5 +323,135 @@ StringData getSaslCommandUserFieldName() {
return saslCommandUserFieldName;
}
+namespace {
+
+StatusWith<std::shared_ptr<SaslClientSession>> _speculateSaslStart(BSONObjBuilder* isMaster,
+ const std::string& mechanism,
+ const HostAndPort& host,
+ StringData authDB,
+ BSONObj params) {
+ if (mechanism == kMechanismSaslPlain) {
+ return {ErrorCodes::BadValue, "PLAIN mechanism not supported with speculativeSaslStart"};
+ }
+
+ std::shared_ptr<SaslClientSession> session(SaslClientSession::create(mechanism));
+ auto status = saslConfigureSession(session.get(), host, authDB, params);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ std::string payload;
+ status = session->step("", &payload);
+ if (!status.isOK()) {
+ return status;
+ }
+
+ BSONObjBuilder saslStart;
+ saslStart.append("saslStart", 1);
+ saslStart.append("mechanism", mechanism);
+ saslStart.appendBinData("payload", int(payload.size()), BinDataGeneral, payload.c_str());
+ saslStart.append("db", authDB);
+ isMaster->append(kSpeculativeAuthenticate, saslStart.obj());
+
+ return session;
+}
+
+StatusWith<SpeculativeAuthType> _speculateAuth(
+ BSONObjBuilder* isMaster,
+ const std::string& mechanism,
+ const HostAndPort& host,
+ StringData authDB,
+ BSONObj params,
+ std::shared_ptr<SaslClientSession>* saslClientSession) {
+ if (mechanism == kMechanismMongoX509) {
+ // MONGODB-X509
+ isMaster->append(kSpeculativeAuthenticate,
+ BSON(kAuthenticateCommand << "1" << saslCommandMechanismFieldName
+ << mechanism << saslCommandUserDBFieldName
+ << "$external"));
+ return SpeculativeAuthType::kAuthenticate;
+ }
+
+ // Proceed as if this is a SASL mech and we either have a password,
+ // or we don't need one (e.g. MONGODB-AWS).
+ // Failure is absolutely an option.
+ auto swSaslClientSession = _speculateSaslStart(isMaster, mechanism, host, authDB, params);
+ if (!swSaslClientSession.isOK()) {
+ return swSaslClientSession.getStatus();
+ }
+
+ // It's okay to fail, the non-speculative auth flow will try again.
+ *saslClientSession = std::move(swSaslClientSession.getValue());
+ return SpeculativeAuthType::kSaslStart;
+}
+
+std::string getBSONString(BSONObj container, StringData field) {
+ auto elem = container[field];
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "Field '" << field << "' must be of type string",
+ elem.type() == String);
+ return elem.String();
+}
+} // namespace
+
+SpeculativeAuthType speculateAuth(BSONObjBuilder* isMasterRequest,
+ const MongoURI& uri,
+ std::shared_ptr<SaslClientSession>* saslClientSession) {
+ auto mechanism = uri.getOption("authMechanism").get_value_or(kMechanismScramSha256.toString());
+
+ auto optParams = uri.makeAuthObjFromOptions(LATEST_WIRE_VERSION, {mechanism});
+ if (!optParams) {
+ return SpeculativeAuthType::kNone;
+ }
+
+ auto params = std::move(optParams.get());
+
+ auto ret = _speculateAuth(isMasterRequest,
+ mechanism,
+ uri.getServers().front(),
+ uri.getAuthenticationDatabase(),
+ params,
+ saslClientSession);
+ if (!ret.isOK()) {
+ // Ignore error, fallback on explicit auth.
+ return SpeculativeAuthType::kNone;
+ }
+
+ return ret.getValue();
+}
+
+SpeculativeAuthType speculateInternalAuth(
+ BSONObjBuilder* isMasterRequest, std::shared_ptr<SaslClientSession>* saslClientSession) try {
+ auto params = getInternalAuthParams(0, kMechanismScramSha256.toString());
+ if (params.isEmpty()) {
+ return SpeculativeAuthType::kNone;
+ }
+
+ auto mechanism = getBSONString(params, saslCommandMechanismFieldName);
+ auto authDB = getBSONString(params, saslCommandUserDBFieldName);
+
+ auto ret = _speculateAuth(
+ isMasterRequest, mechanism, HostAndPort(), authDB, params, saslClientSession);
+ if (!ret.isOK()) {
+ return SpeculativeAuthType::kNone;
+ }
+
+ return ret.getValue();
+} catch (...) {
+ // Swallow any exception and fallback on explicit auth.
+ return SpeculativeAuthType::kNone;
+}
+
+std::string getInternalAuthDB() {
+ stdx::lock_guard<Latch> lk(internalAuthKeysMutex);
+
+ if (!internalAuthParams.isEmpty()) {
+ return getBSONString(internalAuthParams, saslCommandUserDBFieldName);
+ }
+
+ auto isu = internalSecurity.user;
+ return isu ? isu->getName().getDB().toString() : "admin";
+}
+
} // namespace auth
} // namespace mongo
diff --git a/src/mongo/client/authenticate.h b/src/mongo/client/authenticate.h
index 5578cb27f53..326b3c1fc5d 100644
--- a/src/mongo/client/authenticate.h
+++ b/src/mongo/client/authenticate.h
@@ -30,11 +30,14 @@
#pragma once
#include <functional>
+#include <memory>
#include <string>
#include "mongo/base/status_with.h"
#include "mongo/base/string_data.h"
#include "mongo/bson/bsonobj.h"
+#include "mongo/client/mongo_uri.h"
+#include "mongo/client/sasl_client_session.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/executor/remote_command_response.h"
#include "mongo/rpc/op_msg.h"
@@ -67,6 +70,9 @@ constexpr auto kMechanismScramSha256 = "SCRAM-SHA-256"_sd;
constexpr auto kMechanismMongoAWS = "MONGODB-AWS"_sd;
constexpr auto kInternalAuthFallbackMechanism = kMechanismScramSha1;
+constexpr auto kSpeculativeAuthenticate = "speculativeAuthenticate"_sd;
+constexpr auto kAuthenticateCommand = "authenticate"_sd;
+
/**
* Authenticate a user.
*
@@ -168,5 +174,34 @@ StringData getSaslCommandUserDBFieldName();
*/
StringData getSaslCommandUserFieldName();
+/**
+ * Which type of speculative authentication was performed (if any).
+ */
+enum class SpeculativeAuthType {
+ kNone,
+ kAuthenticate,
+ kSaslStart,
+};
+
+/**
+ * Constructs a "speculativeAuthenticate" or "speculativeSaslStart"
+ * payload for an isMaster request based on a given URI.
+ */
+SpeculativeAuthType speculateAuth(BSONObjBuilder* isMasterRequest,
+ const MongoURI& uri,
+ std::shared_ptr<SaslClientSession>* saslClientSession);
+
+/**
+ * Constructs a "speculativeAuthenticate" or "speculativeSaslStart"
+ * payload for an isMaster request using internal (intracluster) authentication.
+ */
+SpeculativeAuthType speculateInternalAuth(BSONObjBuilder* isMasterRequest,
+ std::shared_ptr<SaslClientSession>* saslClientSession);
+
+/**
+ * Returns the AuthDB used by internal authentication.
+ */
+std::string getInternalAuthDB();
+
} // namespace auth
} // namespace mongo
diff --git a/src/mongo/client/dbclient_base.h b/src/mongo/client/dbclient_base.h
index b9c2f60b28f..ac4d302d172 100644
--- a/src/mongo/client/dbclient_base.h
+++ b/src/mongo/client/dbclient_base.h
@@ -681,6 +681,10 @@ public:
virtual bool isMongos() const = 0;
+ virtual bool authenticatedDuringConnect() const {
+ return false;
+ }
+
/**
* Parses command replies and runs them through the metadata reader.
* This is virtual and non-const to allow subclasses to act on failures.
diff --git a/src/mongo/client/dbclient_connection.cpp b/src/mongo/client/dbclient_connection.cpp
index d43c788affa..5a9d29859cc 100644
--- a/src/mongo/client/dbclient_connection.cpp
+++ b/src/mongo/client/dbclient_connection.cpp
@@ -50,7 +50,10 @@
#include "mongo/client/constants.h"
#include "mongo/client/dbclient_cursor.h"
#include "mongo/client/replica_set_monitor.h"
+#include "mongo/client/sasl_client_authenticate.h"
+#include "mongo/client/sasl_client_session.h"
#include "mongo/config.h"
+#include "mongo/db/auth/sasl_command_constants.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/client.h"
#include "mongo/db/commands.h"
@@ -108,78 +111,137 @@ private:
const rpc::ProtocolSet _oldProtos;
};
+StatusWith<bool> completeSpeculativeAuth(DBClientConnection* conn,
+ auth::SpeculativeAuthType speculativeAuthType,
+ std::shared_ptr<SaslClientSession> session,
+ const MongoURI& uri,
+ BSONObj isMaster) {
+ auto specAuthElem = isMaster[auth::kSpeculativeAuthenticate];
+ if (specAuthElem.eoo()) {
+ return false;
+ }
+
+ if (speculativeAuthType == auth::SpeculativeAuthType::kNone) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "Unexpected isMaster." << auth::kSpeculativeAuthenticate
+ << " reply"};
+ }
+
+ if (specAuthElem.type() != Object) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "isMaster." << auth::kSpeculativeAuthenticate
+ << " reply must be an object"};
+ }
+
+ auto specAuth = specAuthElem.Obj();
+ if (specAuth.isEmpty()) {
+ return {ErrorCodes::BadValue,
+ str::stream() << "isMaster." << auth::kSpeculativeAuthenticate
+ << " reply must be a non-empty obejct"};
+ }
+
+ if (speculativeAuthType == auth::SpeculativeAuthType::kAuthenticate) {
+ return specAuth.hasField(saslCommandUserFieldName);
+ }
+
+ invariant(speculativeAuthType == auth::SpeculativeAuthType::kSaslStart);
+
+ const auto hook = [conn](OpMsgRequest request) -> Future<BSONObj> {
+ try {
+ auto ret = conn->runCommand(std::move(request));
+ auto status = getStatusFromCommandResult(ret->getCommandReply());
+ if (!status.isOK()) {
+ return status;
+ }
+ return ret->getCommandReply();
+ } catch (const DBException& e) {
+ return e.toStatus();
+ }
+ };
+
+ return asyncSaslConversation(hook,
+ session,
+ BSON(saslContinueCommandName << 1),
+ specAuth,
+ uri.getAuthenticationDatabase(),
+ kSaslClientLogLevelDefault)
+ .getNoThrow()
+ .isOK();
+}
+
/**
* Initializes the wire version of conn, and returns the isMaster reply.
*/
-executor::RemoteCommandResponse initWireVersion(DBClientConnection* conn,
- StringData applicationName,
- const MongoURI& uri,
- std::vector<std::string>* saslMechsForAuth) {
- try {
- // We need to force the usage of OP_QUERY on this command, even if we have previously
- // detected support for OP_MSG on a connection. This is necessary to handle the case
- // where we reconnect to an older version of MongoDB running at the same host/port.
- ScopedForceOpQuery forceOpQuery{conn};
-
- BSONObjBuilder bob;
- bob.append("isMaster", 1);
-
- if (!uri.getUser().empty()) {
- const auto authDatabase = uri.getAuthenticationDatabase();
- UserName user(uri.getUser(), authDatabase);
- bob.append("saslSupportedMechs", user.getUnambiguousName());
- }
+executor::RemoteCommandResponse initWireVersion(
+ DBClientConnection* conn,
+ StringData applicationName,
+ const MongoURI& uri,
+ std::vector<std::string>* saslMechsForAuth,
+ auth::SpeculativeAuthType* speculativeAuthType,
+ std::shared_ptr<SaslClientSession>* saslClientSession) try {
+ // We need to force the usage of OP_QUERY on this command, even if we have previously
+ // detected support for OP_MSG on a connection. This is necessary to handle the case
+ // where we reconnect to an older version of MongoDB running at the same host/port.
+ ScopedForceOpQuery forceOpQuery{conn};
- if (getTestCommandsEnabled()) {
- // Only include the host:port of this process in the isMaster command request if test
- // commands are enabled. mongobridge uses this field to identify the process opening a
- // connection to it.
- StringBuilder sb;
- sb << getHostName() << ':' << serverGlobalParams.port;
- bob.append("hostInfo", sb.str());
- }
+ BSONObjBuilder bob;
+ bob.append("isMaster", 1);
- auto versionString = VersionInfoInterface::instance().version();
+ *speculativeAuthType = auth::speculateAuth(&bob, uri, saslClientSession);
+ if (!uri.getUser().empty()) {
+ UserName user(uri.getUser(), uri.getAuthenticationDatabase());
+ bob.append("saslSupportedMechs", user.getUnambiguousName());
+ }
- Status serializeStatus = ClientMetadata::serialize(
- "MongoDB Internal Client", versionString, applicationName, &bob);
- if (!serializeStatus.isOK()) {
- return serializeStatus;
- }
+ if (getTestCommandsEnabled()) {
+ // Only include the host:port of this process in the isMaster command request if test
+ // commands are enabled. mongobridge uses this field to identify the process opening a
+ // connection to it.
+ StringBuilder sb;
+ sb << getHostName() << ':' << serverGlobalParams.port;
+ bob.append("hostInfo", sb.str());
+ }
- conn->getCompressorManager().clientBegin(&bob);
+ auto versionString = VersionInfoInterface::instance().version();
- if (WireSpec::instance().isInternalClient) {
- WireSpec::appendInternalClientWireVersion(WireSpec::instance().outgoing, &bob);
- }
+ Status serializeStatus =
+ ClientMetadata::serialize("MongoDB Internal Client", versionString, applicationName, &bob);
+ if (!serializeStatus.isOK()) {
+ return serializeStatus;
+ }
- Date_t start{Date_t::now()};
- auto result = conn->runCommand(OpMsgRequest::fromDBAndBody("admin", bob.obj()));
- Date_t finish{Date_t::now()};
+ conn->getCompressorManager().clientBegin(&bob);
- BSONObj isMasterObj = result->getCommandReply().getOwned();
+ if (WireSpec::instance().isInternalClient) {
+ WireSpec::appendInternalClientWireVersion(WireSpec::instance().outgoing, &bob);
+ }
- if (isMasterObj.hasField("minWireVersion") && isMasterObj.hasField("maxWireVersion")) {
- int minWireVersion = isMasterObj["minWireVersion"].numberInt();
- int maxWireVersion = isMasterObj["maxWireVersion"].numberInt();
- conn->setWireVersions(minWireVersion, maxWireVersion);
- }
+ Date_t start{Date_t::now()};
+ auto result = conn->runCommand(OpMsgRequest::fromDBAndBody("admin", bob.obj()));
+ Date_t finish{Date_t::now()};
- if (isMasterObj.hasField("saslSupportedMechs") &&
- isMasterObj["saslSupportedMechs"].type() == Array) {
- auto array = isMasterObj["saslSupportedMechs"].Array();
- for (const auto& elem : array) {
- saslMechsForAuth->push_back(elem.checkAndGetStringData().toString());
- }
+ BSONObj isMasterObj = result->getCommandReply().getOwned();
+
+ if (isMasterObj.hasField("minWireVersion") && isMasterObj.hasField("maxWireVersion")) {
+ int minWireVersion = isMasterObj["minWireVersion"].numberInt();
+ int maxWireVersion = isMasterObj["maxWireVersion"].numberInt();
+ conn->setWireVersions(minWireVersion, maxWireVersion);
+ }
+
+ if (isMasterObj.hasField("saslSupportedMechs") &&
+ isMasterObj["saslSupportedMechs"].type() == Array) {
+ auto array = isMasterObj["saslSupportedMechs"].Array();
+ for (const auto& elem : array) {
+ saslMechsForAuth->push_back(elem.checkAndGetStringData().toString());
}
+ }
- conn->getCompressorManager().clientFinish(isMasterObj);
+ conn->getCompressorManager().clientFinish(isMasterObj);
- return executor::RemoteCommandResponse{std::move(isMasterObj), finish - start};
+ return executor::RemoteCommandResponse{std::move(isMasterObj), finish - start};
- } catch (...) {
- return exceptionToStatus();
- }
+} catch (...) {
+ return exceptionToStatus();
}
} // namespace
@@ -231,7 +293,10 @@ Status DBClientConnection::connect(const HostAndPort& serverAddress, StringData
// access the application name, do it through the _applicationName member.
_applicationName = applicationName.toString();
- auto swIsMasterReply = initWireVersion(this, _applicationName, _uri, &_saslMechsForAuth);
+ auto speculativeAuthType = auth::SpeculativeAuthType::kNone;
+ std::shared_ptr<SaslClientSession> saslClientSession;
+ auto swIsMasterReply = initWireVersion(
+ this, _applicationName, _uri, &_saslMechsForAuth, &speculativeAuthType, &saslClientSession);
if (!swIsMasterReply.isOK()) {
_markFailed(kSetFlag);
return swIsMasterReply.status;
@@ -299,6 +364,18 @@ Status DBClientConnection::connect(const HostAndPort& serverAddress, StringData
}
}
+ {
+ auto swAuth = completeSpeculativeAuth(
+ this, speculativeAuthType, saslClientSession, _uri, swIsMasterReply.data);
+ if (!swAuth.isOK()) {
+ return swAuth.getStatus();
+ }
+
+ if (swAuth.getValue()) {
+ _authenticatedDuringConnect = true;
+ }
+ }
+
return Status::OK();
}
diff --git a/src/mongo/client/dbclient_connection.h b/src/mongo/client/dbclient_connection.h
index 9dccef0ea42..5d76701bc59 100644
--- a/src/mongo/client/dbclient_connection.h
+++ b/src/mongo/client/dbclient_connection.h
@@ -293,6 +293,10 @@ public:
Status authenticateInternalUser() override;
+ bool authenticatedDuringConnect() const override {
+ return _authenticatedDuringConnect;
+ }
+
protected:
int _minWireVersion{0};
int _maxWireVersion{0};
@@ -349,6 +353,8 @@ private:
MessageCompressorManager _compressorManager;
MongoURI _uri;
+
+ bool _authenticatedDuringConnect = false;
};
BSONElement getErrField(const BSONObj& result);
diff --git a/src/mongo/client/dbclient_rs.cpp b/src/mongo/client/dbclient_rs.cpp
index e241efc05ea..25fd973fc8f 100644
--- a/src/mongo/client/dbclient_rs.cpp
+++ b/src/mongo/client/dbclient_rs.cpp
@@ -741,7 +741,9 @@ DBClientConnection* DBClientReplicaSet::selectNodeUsingTags(
_lastSlaveOkConn->setReplyMetadataReader(getReplyMetadataReader());
if (_authPooledSecondaryConn) {
- _authConnection(_lastSlaveOkConn.get());
+ if (!_lastSlaveOkConn->authenticatedDuringConnect()) {
+ _authConnection(_lastSlaveOkConn.get());
+ }
} else {
// Mongos pooled connections are authenticated through ShardingConnectionHook::onCreate()
}
diff --git a/src/mongo/client/mongo_uri.cpp b/src/mongo/client/mongo_uri.cpp
index 433c33c8e61..73c8ed8de76 100644
--- a/src/mongo/client/mongo_uri.cpp
+++ b/src/mongo/client/mongo_uri.cpp
@@ -39,11 +39,13 @@
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/find_iterator.hpp>
#include <boost/algorithm/string/predicate.hpp>
+#include <boost/algorithm/string/split.hpp>
#include <boost/range/algorithm/count.hpp>
#include "mongo/base/status_with.h"
#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/client/sasl_client_authenticate.h"
+#include "mongo/db/auth/sasl_command_constants.h"
#include "mongo/db/namespace_string.h"
#include "mongo/stdx/utility.h"
#include "mongo/util/dns_name.h"
@@ -61,6 +63,7 @@ constexpr std::array<char, 16> hexits{
// a `std::map<std::string, std::string>` as the other parameter.
const std::vector<std::pair<std::string, std::string>> permittedTXTOptions = {{"authSource"s, ""s},
{"replicaSet"s, ""s}};
+
} // namespace
/**
@@ -571,4 +574,128 @@ std::string MongoURI::canonicalizeURIAsString() const {
}
return uri.str();
}
+
+namespace {
+constexpr auto kAuthMechanismPropertiesKey = "mechanism_properties"_sd;
+
+constexpr auto kAuthServiceName = "SERVICE_NAME"_sd;
+constexpr auto kAuthServiceRealm = "SERVICE_REALM"_sd;
+constexpr auto kAuthAwsSessionToken = "AWS_SESSION_TOKEN"_sd;
+
+constexpr std::array<StringData, 3> kSupportedAuthMechanismProperties = {
+ kAuthServiceName, kAuthServiceRealm, kAuthAwsSessionToken};
+
+BSONObj parseAuthMechanismProperties(const std::string& propStr) {
+ BSONObjBuilder bob;
+ std::vector<std::string> props;
+ boost::algorithm::split(props, propStr, boost::algorithm::is_any_of(",:"));
+ for (std::vector<std::string>::const_iterator it = props.begin(); it != props.end(); ++it) {
+ std::string prop((boost::algorithm::to_upper_copy(*it))); // normalize case
+ uassert(ErrorCodes::FailedToParse,
+ str::stream() << "authMechanismProperty: " << *it << " is not supported",
+ std::count(std::begin(kSupportedAuthMechanismProperties),
+ std::end(kSupportedAuthMechanismProperties),
+ StringData(prop)));
+ ++it;
+ uassert(ErrorCodes::FailedToParse,
+ str::stream() << "authMechanismProperty: " << prop << " must have a value",
+ it != props.end());
+ bob.append(prop, *it);
+ }
+ return bob.obj();
+}
+} // namespace
+
+boost::optional<BSONObj> MongoURI::makeAuthObjFromOptions(
+ int maxWireVersion, const std::vector<std::string>& saslMechsForAuth) const {
+ // Usually, a username is required to authenticate.
+ // However X509 based authentication may, and typically does,
+ // omit the username, inferring it from the client certificate instead.
+ bool usernameRequired = true;
+
+ BSONObjBuilder bob;
+ if (!_password.empty()) {
+ bob.append(saslCommandPasswordFieldName, _password);
+ }
+
+ auto it = _options.find("authSource");
+ if (it != _options.end()) {
+ bob.append(saslCommandUserDBFieldName, it->second);
+ } else if (!_database.empty()) {
+ bob.append(saslCommandUserDBFieldName, _database);
+ } else {
+ bob.append(saslCommandUserDBFieldName, "admin");
+ }
+
+ it = _options.find("authMechanism");
+ if (it != _options.end()) {
+ bob.append(saslCommandMechanismFieldName, it->second);
+ if (it->second == auth::kMechanismMongoX509 || it->second == auth::kMechanismMongoAWS) {
+ usernameRequired = false;
+ }
+ } else if (!saslMechsForAuth.empty()) {
+ if (std::find(saslMechsForAuth.begin(),
+ saslMechsForAuth.end(),
+ auth::kMechanismScramSha256) != saslMechsForAuth.end()) {
+ bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha256);
+ } else {
+ bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha1);
+ }
+ } else if (maxWireVersion >= 3) {
+ bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha1);
+ } else {
+ bob.append(saslCommandMechanismFieldName, auth::kMechanismMongoCR);
+ }
+
+ if (usernameRequired && _user.empty()) {
+ return boost::none;
+ }
+
+ std::string username(_user); // may have to tack on service realm before we append
+
+ it = _options.find("authMechanismProperties");
+ if (it != _options.end()) {
+ BSONObj parsed(parseAuthMechanismProperties(it->second));
+
+ bool hasNameProp = parsed.hasField(kAuthServiceName);
+ bool hasRealmProp = parsed.hasField(kAuthServiceRealm);
+
+ uassert(ErrorCodes::FailedToParse,
+ "Cannot specify both gssapiServiceName and SERVICE_NAME",
+ !(hasNameProp && _options.count("gssapiServiceName")));
+ // we append the parsed object so that mechanisms that don't accept it can assert.
+ bob.append(kAuthMechanismPropertiesKey, parsed);
+ // we still append using the old way the SASL code expects it
+ if (hasNameProp) {
+ bob.append(saslCommandServiceNameFieldName, parsed[kAuthServiceName].String());
+ }
+ // if we specified a realm, we just append it to the username as the SASL code
+ // expects it that way.
+ if (hasRealmProp) {
+ if (username.empty()) {
+ // In practice, this won't actually occur since
+ // this block corresponds to GSSAPI, while username
+ // may only be omitted with MOGNODB-X509.
+ return boost::none;
+ }
+ username.append("@").append(parsed[kAuthServiceRealm].String());
+ }
+
+ if (parsed.hasField(kAuthAwsSessionToken)) {
+ bob.append(saslCommandIamSessionToken, parsed[kAuthAwsSessionToken].String());
+ }
+ }
+
+ it = _options.find("gssapiServiceName");
+ if (it != _options.end()) {
+ bob.append(saslCommandServiceNameFieldName, it->second);
+ }
+
+ if (!username.empty()) {
+ bob.append("user", username);
+ }
+
+ return bob.obj();
+}
+
} // namespace mongo
diff --git a/src/mongo/client/mongo_uri.h b/src/mongo/client/mongo_uri.h
index c69b5e555c7..5b9f73039f8 100644
--- a/src/mongo/client/mongo_uri.h
+++ b/src/mongo/client/mongo_uri.h
@@ -45,6 +45,7 @@
#include "mongo/util/net/hostandport.h"
namespace mongo {
+
/**
* Encode a string for embedding in a URI.
* Replaces reserved bytes with %xx sequences.
@@ -261,6 +262,9 @@ public:
friend StringBuilder& operator<<(StringBuilder&, const MongoURI&);
+ boost::optional<BSONObj> makeAuthObjFromOptions(
+ int maxWireVersion, const std::vector<std::string>& saslMechsForAuth) const;
+
private:
MongoURI(ConnectionString connectString,
const std::string& user,
@@ -277,9 +281,6 @@ private:
_sslMode(sslMode),
_options(std::move(options)) {}
- boost::optional<BSONObj> _makeAuthObjFromOptions(
- int maxWireVersion, const std::vector<std::string>& saslMechsForAuth) const;
-
static MongoURI parseImpl(const std::string& url);
ConnectionString _connectString;
diff --git a/src/mongo/client/mongo_uri_connect.cpp b/src/mongo/client/mongo_uri_connect.cpp
index 70109df8499..ae556d20aa9 100644
--- a/src/mongo/client/mongo_uri_connect.cpp
+++ b/src/mongo/client/mongo_uri_connect.cpp
@@ -31,150 +31,12 @@
#include "mongo/client/mongo_uri.h"
-#include "mongo/base/status_with.h"
-#include "mongo/bson/bsonobjbuilder.h"
#include "mongo/client/authenticate.h"
#include "mongo/client/dbclient_base.h"
-#include "mongo/db/auth/sasl_command_constants.h"
-#include "mongo/util/password_digest.h"
#include "mongo/util/str.h"
-#include <boost/algorithm/string/case_conv.hpp>
-#include <boost/algorithm/string/classification.hpp>
-#include <boost/algorithm/string/predicate.hpp>
-#include <boost/algorithm/string/split.hpp>
-
-#include <iterator>
-
namespace mongo {
-namespace {
-const char kAuthMechanismPropertiesKey[] = "mechanism_properties";
-
-// CANONICALIZE_HOST_NAME is currently unsupported
-const char kAuthServiceName[] = "SERVICE_NAME";
-const char kAuthServiceRealm[] = "SERVICE_REALM";
-const char kAuthAwsSessionToken[] = "AWS_SESSION_TOKEN";
-
-const char kAuthMechDefault[] = "DEFAULT";
-
-const char* const kSupportedAuthMechanismProperties[] = {
- kAuthServiceName, kAuthServiceRealm, kAuthAwsSessionToken};
-
-BSONObj parseAuthMechanismProperties(const std::string& propStr) {
- BSONObjBuilder bob;
- std::vector<std::string> props;
- boost::algorithm::split(props, propStr, boost::algorithm::is_any_of(",:"));
- for (std::vector<std::string>::const_iterator it = props.begin(); it != props.end(); ++it) {
- std::string prop((boost::algorithm::to_upper_copy(*it))); // normalize case
- uassert(ErrorCodes::FailedToParse,
- str::stream() << "authMechanismProperty: " << *it << " is not supported",
- std::count(kSupportedAuthMechanismProperties,
- std::end(kSupportedAuthMechanismProperties),
- prop));
- ++it;
- uassert(ErrorCodes::FailedToParse,
- str::stream() << "authMechanismProperty: " << prop << " must have a value",
- it != props.end());
- bob.append(prop, *it);
- }
- return bob.obj();
-}
-
-} // namespace
-
-boost::optional<BSONObj> MongoURI::_makeAuthObjFromOptions(
- int maxWireVersion, const std::vector<std::string>& saslMechsForAuth) const {
- // Usually, a username is required to authenticate.
- // However X509 based authentication may, and typically does,
- // omit the username, inferring it from the client certificate instead.
- bool usernameRequired = true;
-
- BSONObjBuilder bob;
- if (!_password.empty()) {
- bob.append(saslCommandPasswordFieldName, _password);
- }
-
- auto it = _options.find("authSource");
- if (it != _options.end()) {
- bob.append(saslCommandUserDBFieldName, it->second);
- } else if (!_database.empty()) {
- bob.append(saslCommandUserDBFieldName, _database);
- } else {
- bob.append(saslCommandUserDBFieldName, "admin");
- }
-
- it = _options.find("authMechanism");
- if (it != _options.end()) {
- bob.append(saslCommandMechanismFieldName, it->second);
- if (it->second == auth::kMechanismMongoX509 || it->second == auth::kMechanismMongoAWS) {
- usernameRequired = false;
- }
- } else if (!saslMechsForAuth.empty()) {
- if (std::find(saslMechsForAuth.begin(),
- saslMechsForAuth.end(),
- auth::kMechanismScramSha256) != saslMechsForAuth.end()) {
- bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha256);
- } else {
- bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha1);
- }
- } else if (maxWireVersion >= 3) {
- bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha1);
- } else {
- bob.append(saslCommandMechanismFieldName, auth::kMechanismMongoCR);
- }
-
- if (usernameRequired && _user.empty()) {
- return boost::none;
- }
-
- std::string username(_user); // may have to tack on service realm before we append
-
- it = _options.find("authMechanismProperties");
- if (it != _options.end()) {
- BSONObj parsed(parseAuthMechanismProperties(it->second));
-
- bool hasNameProp = parsed.hasField(kAuthServiceName);
- bool hasRealmProp = parsed.hasField(kAuthServiceRealm);
-
- uassert(ErrorCodes::FailedToParse,
- "Cannot specify both gssapiServiceName and SERVICE_NAME",
- !(hasNameProp && _options.count("gssapiServiceName")));
- // we append the parsed object so that mechanisms that don't accept it can assert.
- bob.append(kAuthMechanismPropertiesKey, parsed);
- // we still append using the old way the SASL code expects it
- if (hasNameProp) {
- bob.append(saslCommandServiceNameFieldName, parsed[kAuthServiceName].String());
- }
- // if we specified a realm, we just append it to the username as the SASL code
- // expects it that way.
- if (hasRealmProp) {
- if (username.empty()) {
- // In practice, this won't actually occur since
- // this block corresponds to GSSAPI, while username
- // may only be omitted with MOGNODB-X509.
- return boost::none;
- }
- username.append("@").append(parsed[kAuthServiceRealm].String());
- }
-
- if (parsed.hasField(kAuthAwsSessionToken)) {
- bob.append(saslCommandIamSessionToken, parsed[kAuthAwsSessionToken].String());
- }
- }
-
- it = _options.find("gssapiServiceName");
- if (it != _options.end()) {
- bob.append(saslCommandServiceNameFieldName, it->second);
- }
-
- if (!username.empty()) {
- bob.append("user", username);
- }
-
- return bob.obj();
-}
-
DBClientBase* MongoURI::connect(StringData applicationName,
std::string& errmsg,
boost::optional<double> socketTimeoutSecs) const {
@@ -200,10 +62,12 @@ DBClientBase* MongoURI::connect(StringData applicationName,
return ret.release();
}
- auto optAuthObj =
- _makeAuthObjFromOptions(ret->getMaxWireVersion(), ret->getIsMasterSaslMechanisms());
- if (optAuthObj) {
- ret->auth(optAuthObj.get());
+ if (!ret->authenticatedDuringConnect()) {
+ auto optAuthObj =
+ makeAuthObjFromOptions(ret->getMaxWireVersion(), ret->getIsMasterSaslMechanisms());
+ if (optAuthObj) {
+ ret->auth(optAuthObj.get());
+ }
}
return ret.release();
diff --git a/src/mongo/client/sasl_client_authenticate.h b/src/mongo/client/sasl_client_authenticate.h
index 4b342a41a99..7ac1419308f 100644
--- a/src/mongo/client/sasl_client_authenticate.h
+++ b/src/mongo/client/sasl_client_authenticate.h
@@ -29,14 +29,19 @@
#pragma once
+#include <memory>
+#include <string>
+
#include "mongo/base/status.h"
#include "mongo/bson/bsontypes.h"
#include "mongo/client/authenticate.h"
#include "mongo/executor/remote_command_request.h"
#include "mongo/executor/remote_command_response.h"
+#include "mongo/util/future.h"
namespace mongo {
class BSONObj;
+class SaslClientSession;
/**
* Attempts to authenticate "client" using the SASL protocol.
@@ -83,4 +88,30 @@ extern Future<void> (*saslClientAuthenticate)(auth::RunCommandHook runCommand,
* into "*payload". In all other cases, returns
*/
Status saslExtractPayload(const BSONObj& cmdObj, std::string* payload, BSONType* type);
+
+// Default log level on the client for SASL log messages.
+constexpr int kSaslClientLogLevelDefault = 4;
+
+/**
+ * Configures and initializes "session" to perform the client side of a
+ * SASL conversation over connection "client".
+ *
+ * "saslParameters" is a BSON document providing the necessary configuration information.
+ *
+ * Returns Status::OK() on success.
+ */
+Status saslConfigureSession(SaslClientSession* session,
+ const HostAndPort& hostname,
+ StringData targetDatabase,
+ const BSONObj& saslParameters);
+
+/**
+ * Continue a previously started sasl session and proceed until completion.
+ */
+Future<void> asyncSaslConversation(auth::RunCommandHook runCommand,
+ const std::shared_ptr<SaslClientSession>& session,
+ const BSONObj& saslCommandPrefix,
+ const BSONObj& inputObj,
+ std::string targetDatabase,
+ int saslLogLevel);
} // namespace mongo
diff --git a/src/mongo/client/sasl_client_authenticate_impl.cpp b/src/mongo/client/sasl_client_authenticate_impl.cpp
index 61dfbefd446..860f913971b 100644
--- a/src/mongo/client/sasl_client_authenticate_impl.cpp
+++ b/src/mongo/client/sasl_client_authenticate_impl.cpp
@@ -62,18 +62,20 @@ using std::endl;
namespace {
-// Default log level on the client for SASL log messages.
-const int defaultSaslClientLogLevel = 4;
-
-const char* const saslClientLogFieldName = "clientLogLevel";
+constexpr auto saslClientLogFieldName = "clientLogLevel"_sd;
int getSaslClientLogLevel(const BSONObj& saslParameters) {
- int saslLogLevel = defaultSaslClientLogLevel;
+ int saslLogLevel = kSaslClientLogLevelDefault;
BSONElement saslLogElement = saslParameters[saslClientLogFieldName];
- if (saslLogElement.trueValue())
+
+ if (saslLogElement.trueValue()) {
saslLogLevel = 1;
- if (saslLogElement.isNumber())
+ }
+
+ if (saslLogElement.isNumber()) {
saslLogLevel = saslLogElement.numberInt();
+ }
+
return saslLogLevel;
}
@@ -109,19 +111,12 @@ Status extractPassword(const BSONObj& saslParameters,
}
return Status::OK();
}
+} // namespace
-/**
- * Configures "session" to perform the client side of a SASL conversation over connection
- * "client".
- *
- * "saslParameters" is a BSON document providing the necessary configuration information.
- *
- * Returns Status::OK() on success.
- */
-Status configureSession(SaslClientSession* session,
- const HostAndPort& hostname,
- StringData targetDatabase,
- const BSONObj& saslParameters) {
+Status saslConfigureSession(SaslClientSession* session,
+ const HostAndPort& hostname,
+ StringData targetDatabase,
+ const BSONObj& saslParameters) {
std::string mechanism;
Status status =
bsonExtractStringField(saslParameters, saslCommandMechanismFieldName, &mechanism);
@@ -241,6 +236,7 @@ Future<void> asyncSaslConversation(auth::RunCommandHook runCommand,
});
}
+namespace {
/**
* Driver for the client side of a sasl authentication session, conducted synchronously over
* "client".
@@ -270,7 +266,7 @@ Future<void> saslClientAuthenticateImpl(auth::RunCommandHook runCommand,
// Come C++14, we should be able to do this in a nicer way.
std::shared_ptr<SaslClientSession> session(SaslClientSession::create(mechanism));
- status = configureSession(session.get(), hostname, targetDatabase, saslParameters);
+ status = saslConfigureSession(session.get(), hostname, targetDatabase, saslParameters);
if (!status.isOK())
return status;
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript
index 6c579a188eb..a7cce5850bb 100644
--- a/src/mongo/db/auth/SConscript
+++ b/src/mongo/db/auth/SConscript
@@ -245,6 +245,7 @@ env.Library(
'$BUILD_DIR/mongo/db/commands',
'$BUILD_DIR/mongo/db/commands/authentication_commands',
'$BUILD_DIR/mongo/db/commands/test_commands_enabled',
+ '$BUILD_DIR/mongo/db/stats/counters',
'sasl_options_init',
],
)
@@ -271,6 +272,7 @@ env.Library(
],
LIBDEPS=[
'$BUILD_DIR/mongo/base',
+ '$BUILD_DIR/mongo/db/stats/counters',
'$BUILD_DIR/mongo/idl/server_parameter',
],
)
diff --git a/src/mongo/db/auth/authentication_session.h b/src/mongo/db/auth/authentication_session.h
index 5a26c46efd4..ff86ea7ca08 100644
--- a/src/mongo/db/auth/authentication_session.h
+++ b/src/mongo/db/auth/authentication_session.h
@@ -45,8 +45,8 @@ class AuthenticationSession {
AuthenticationSession& operator=(const AuthenticationSession&) = delete;
public:
- explicit AuthenticationSession(std::unique_ptr<ServerMechanismBase> mech)
- : _mech(std::move(mech)) {}
+ explicit AuthenticationSession(std::unique_ptr<ServerMechanismBase> mech, bool speculative)
+ : _mech(std::move(mech)), _speculative(speculative) {}
/**
* Sets the authentication session for the given "client" to "newSession".
@@ -72,8 +72,13 @@ public:
return _mech->setOptions(options);
}
+ bool isSpeculative() const {
+ return _speculative;
+ }
+
private:
std::unique_ptr<ServerMechanismBase> _mech;
+ bool _speculative{false};
};
} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_commands.cpp b/src/mongo/db/auth/sasl_commands.cpp
index 1932b7e9a4e..a6276d3fec9 100644
--- a/src/mongo/db/auth/sasl_commands.cpp
+++ b/src/mongo/db/auth/sasl_commands.cpp
@@ -39,6 +39,7 @@
#include "mongo/bson/mutable/algorithm.h"
#include "mongo/bson/mutable/document.h"
#include "mongo/bson/util/bson_extract.h"
+#include "mongo/client/authenticate.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/db/audit.h"
#include "mongo/db/auth/authentication_session.h"
@@ -51,6 +52,7 @@
#include "mongo/db/commands.h"
#include "mongo/db/commands/authentication_commands.h"
#include "mongo/db/server_options.h"
+#include "mongo/db/stats/counters.h"
#include "mongo/util/base64.h"
#include "mongo/util/log.h"
#include "mongo/util/sequence_util.h"
@@ -125,6 +127,7 @@ public:
CmdSaslStart cmdSaslStart;
CmdSaslContinue cmdSaslContinue;
+
Status buildResponse(const AuthenticationSession* session,
const std::string& responsePayload,
BSONType responsePayloadType,
@@ -212,6 +215,9 @@ Status doSaslStep(OperationContext* opCtx,
<< " on " << mechanism.getAuthenticationDatabase() << " from client "
<< opCtx->getClient()->session()->remote();
}
+ if (session->isSpeculative()) {
+ authCounter.incSpeculativeAuthenticateSuccessful(mechanism.mechanismName().toString());
+ }
}
return Status::OK();
}
@@ -220,7 +226,8 @@ StatusWith<std::unique_ptr<AuthenticationSession>> doSaslStart(OperationContext*
const std::string& db,
const BSONObj& cmdObj,
BSONObjBuilder* result,
- std::string* principalName) {
+ std::string* principalName,
+ bool speculative) {
bool autoAuthorize = false;
Status status = bsonExtractBooleanFieldWithDefault(
cmdObj, saslCommandAutoAuthorizeFieldName, autoAuthorizeDefault, &autoAuthorize);
@@ -240,7 +247,16 @@ StatusWith<std::unique_ptr<AuthenticationSession>> doSaslStart(OperationContext*
return swMech.getStatus();
}
- auto session = std::make_unique<AuthenticationSession>(std::move(swMech.getValue()));
+ auto session =
+ std::make_unique<AuthenticationSession>(std::move(swMech.getValue()), speculative);
+
+ if (speculative &&
+ !session->getMechanism().properties().hasAllProperties(
+ SecurityPropertySet({SecurityProperty::kNoPlainText}))) {
+ return {ErrorCodes::BadValue,
+ "Plaintext mechanisms may not be used with speculativeSaslStart"};
+ }
+
auto options = cmdObj["options"];
if (!options.eoo()) {
if (options.type() != Object) {
@@ -280,17 +296,11 @@ Status doSaslContinue(OperationContext* opCtx,
return doSaslStep(opCtx, session, cmdObj, result);
}
-CmdSaslStart::CmdSaslStart() : BasicCommand(saslStartCommandName) {}
-CmdSaslStart::~CmdSaslStart() {}
-
-std::string CmdSaslStart::help() const {
- return "First step in a SASL authentication conversation.";
-}
-
-bool CmdSaslStart::run(OperationContext* opCtx,
- const std::string& db,
- const BSONObj& cmdObj,
- BSONObjBuilder& result) {
+bool runSaslStart(OperationContext* opCtx,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result,
+ bool speculative) {
opCtx->markKillOnClientDisconnect();
Client* client = opCtx->getClient();
AuthenticationSession::set(client, std::unique_ptr<AuthenticationSession>());
@@ -301,7 +311,7 @@ bool CmdSaslStart::run(OperationContext* opCtx,
}
std::string principalName;
- auto swSession = doSaslStart(opCtx, db, cmdObj, &result, &principalName);
+ auto swSession = doSaslStart(opCtx, db, cmdObj, &result, &principalName, speculative);
if (!swSession.isOK() || swSession.getValue()->getMechanism().isSuccess()) {
audit::logAuthentication(
@@ -315,6 +325,20 @@ bool CmdSaslStart::run(OperationContext* opCtx,
return true;
}
+CmdSaslStart::CmdSaslStart() : BasicCommand(saslStartCommandName) {}
+CmdSaslStart::~CmdSaslStart() {}
+
+std::string CmdSaslStart::help() const {
+ return "First step in a SASL authentication conversation.";
+}
+
+bool CmdSaslStart::run(OperationContext* opCtx,
+ const std::string& db,
+ const BSONObj& cmdObj,
+ BSONObjBuilder& result) {
+ return runSaslStart(opCtx, db, cmdObj, result, false);
+}
+
CmdSaslContinue::CmdSaslContinue() : BasicCommand(saslContinueCommandName) {}
CmdSaslContinue::~CmdSaslContinue() {}
@@ -371,4 +395,26 @@ MONGO_INITIALIZER(PreSaslCommands)
}
} // namespace
+
+void doSpeculativeSaslStart(OperationContext* opCtx, BSONObj cmdObj, BSONObjBuilder* result) try {
+ auto mechElem = cmdObj["mechanism"];
+ if (mechElem.type() != String) {
+ return;
+ }
+
+ authCounter.incSpeculativeAuthenticateReceived(mechElem.String());
+
+ auto dbElement = cmdObj["db"];
+ if (dbElement.type() != String) {
+ return;
+ }
+
+ BSONObjBuilder saslStartResult;
+ if (runSaslStart(opCtx, dbElement.String(), cmdObj, saslStartResult, true)) {
+ result->append(auth::kSpeculativeAuthenticate, saslStartResult.obj());
+ }
+} catch (...) {
+ // Treat failure like we never even got a speculative start.
+}
+
} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_commands.h b/src/mongo/db/auth/sasl_commands.h
new file mode 100644
index 00000000000..394e6034b0c
--- /dev/null
+++ b/src/mongo/db/auth/sasl_commands.h
@@ -0,0 +1,42 @@
+/**
+ * Copyright (C) 2018-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#pragma once
+
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
+
+namespace mongo {
+class OperationContext;
+
+/**
+ * Handle isMaster: { speculativeAuthenticate: {...} }
+ */
+void doSpeculativeSaslStart(OperationContext* opCtx, BSONObj cmdObj, BSONObjBuilder* result);
+} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_options.cpp b/src/mongo/db/auth/sasl_options.cpp
index ac23ebf6618..d390ad99524 100644
--- a/src/mongo/db/auth/sasl_options.cpp
+++ b/src/mongo/db/auth/sasl_options.cpp
@@ -29,6 +29,7 @@
#include "mongo/db/auth/sasl_options.h"
#include "mongo/db/auth/sasl_options_gen.h"
+#include "mongo/db/stats/counters.h"
#include "mongo/util/text.h"
@@ -46,4 +47,13 @@ SASLGlobalParams::SASLGlobalParams() {
// Default value for auth failed delay
authFailedDelay.store(0);
}
+
+namespace {
+MONGO_INITIALIZER_WITH_PREREQUISITES(InitSpeculativeCounters, ("EndStartupOptionStorage"))
+(InitializerContext*) {
+ authCounter.initializeMechanismMap(saslGlobalParams.authenticationMechanisms);
+ return Status::OK();
+}
+} // namespace
+
} // namespace mongo
diff --git a/src/mongo/db/auth/sasl_options.idl b/src/mongo/db/auth/sasl_options.idl
index 5c6070e9701..c3753ff8d95 100644
--- a/src/mongo/db/auth/sasl_options.idl
+++ b/src/mongo/db/auth/sasl_options.idl
@@ -36,6 +36,8 @@ global:
server_parameters:
authenticationMechanisms:
+ # Note: mongo/db/stats/counter.cpp makes the assumption that this
+ # setting will never be changed at runtime.
description: "The set of accepted authentication mechanisms"
set_at: startup
default:
diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript
index 568e23c6272..679571a308f 100644
--- a/src/mongo/db/commands/SConscript
+++ b/src/mongo/db/commands/SConscript
@@ -192,6 +192,7 @@ env.Library(
'$BUILD_DIR/mongo/db/auth/sasl_options',
'$BUILD_DIR/mongo/db/auth/user_document_parser',
'$BUILD_DIR/mongo/db/commands',
+ '$BUILD_DIR/mongo/db/stats/counters',
'$BUILD_DIR/mongo/util/net/ssl_manager',
]
)
diff --git a/src/mongo/db/commands/authentication_commands.cpp b/src/mongo/db/commands/authentication_commands.cpp
index dd273d77faa..d51d9815d08 100644
--- a/src/mongo/db/commands/authentication_commands.cpp
+++ b/src/mongo/db/commands/authentication_commands.cpp
@@ -40,6 +40,7 @@
#include "mongo/base/status.h"
#include "mongo/bson/mutable/algorithm.h"
#include "mongo/bson/mutable/document.h"
+#include "mongo/client/authenticate.h"
#include "mongo/client/sasl_client_authenticate.h"
#include "mongo/config.h"
#include "mongo/db/audit.h"
@@ -52,6 +53,7 @@
#include "mongo/db/commands.h"
#include "mongo/db/commands/test_commands_enabled.h"
#include "mongo/db/operation_context.h"
+#include "mongo/db/stats/counters.h"
#include "mongo/platform/random.h"
#include "mongo/rpc/metadata/client_metadata.h"
#include "mongo/rpc/metadata/client_metadata_ismaster.h"
@@ -360,4 +362,24 @@ void disableAuthMechanism(StringData authMechanism) {
}
}
+void doSpeculativeAuthenticate(OperationContext* opCtx,
+ BSONObj cmdObj,
+ BSONObjBuilder* result) try {
+ auto mechElem = cmdObj["mechanism"];
+ if (mechElem.type() != String) {
+ return;
+ }
+
+ auto mechanism = mechElem.String();
+ authCounter.incSpeculativeAuthenticateReceived(mechanism);
+
+ BSONObjBuilder authResult;
+ if (cmdAuthenticate.run(opCtx, "$external", cmdObj, authResult)) {
+ authCounter.incSpeculativeAuthenticateSuccessful(mechanism);
+ result->append(auth::kSpeculativeAuthenticate, authResult.obj());
+ }
+} catch (...) {
+ // Treat failure like we never even got a speculative start.
+}
+
} // namespace mongo
diff --git a/src/mongo/db/commands/authentication_commands.h b/src/mongo/db/commands/authentication_commands.h
index d6193a8f4ac..c211b799ef2 100644
--- a/src/mongo/db/commands/authentication_commands.h
+++ b/src/mongo/db/commands/authentication_commands.h
@@ -30,11 +30,16 @@
#pragma once
#include "mongo/base/string_data.h"
+#include "mongo/bson/bsonobj.h"
+#include "mongo/bson/bsonobjbuilder.h"
namespace mongo {
+class OperationContext;
constexpr StringData kX509AuthMechanism = "MONGODB-X509"_sd;
void disableAuthMechanism(StringData authMechanism);
+void doSpeculativeAuthenticate(OperationContext* opCtx, BSONObj isMaster, BSONObjBuilder* result);
+
} // namespace mongo
diff --git a/src/mongo/db/commands/server_status_servers.cpp b/src/mongo/db/commands/server_status_servers.cpp
index e85301b7b7a..a3d23061823 100644
--- a/src/mongo/db/commands/server_status_servers.cpp
+++ b/src/mongo/db/commands/server_status_servers.cpp
@@ -95,7 +95,6 @@ public:
} network;
-#ifdef MONGO_CONFIG_SSL
class Security : public ServerStatusSection {
public:
Security() : ServerStatusSection("security") {}
@@ -106,15 +105,23 @@ public:
BSONObj generateSection(OperationContext* opCtx,
const BSONElement& configElement) const override {
- BSONObj result;
+ BSONObjBuilder result;
+
+ BSONObjBuilder auth;
+ authCounter.append(&auth);
+ result.append("authentication", auth.obj());
+
+#ifdef MONGO_CONFIG_SSL
if (getSSLManager()) {
- result = getSSLManager()->getSSLConfiguration().getServerStatusBSON();
+ getSSLManager()->getSSLConfiguration().getServerStatusBSON(&result);
}
+#endif
- return result;
+ return result.obj();
}
} security;
+#ifdef MONGO_CONFIG_SSL
/**
* Status section of which tls versions connected to MongoDB and completed an SSL handshake.
* Note: Clients are only not counted if they try to connect to the server with a unsupported TLS
diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript
index 6efb849fc51..204e8042cd2 100644
--- a/src/mongo/db/repl/SConscript
+++ b/src/mongo/db/repl/SConscript
@@ -1128,6 +1128,8 @@ env.Library(
'replica_set_messages',
],
LIBDEPS_PRIVATE=[
+ '$BUILD_DIR/mongo/db/auth/authservercommon',
+ '$BUILD_DIR/mongo/db/commands/authentication_commands',
'$BUILD_DIR/mongo/db/commands/server_status',
'$BUILD_DIR/mongo/db/stats/counters',
'$BUILD_DIR/mongo/transport/message_compressor',
diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp
index 1f1d071e29a..6d65ffb2bd4 100644
--- a/src/mongo/db/repl/replication_info.cpp
+++ b/src/mongo/db/repl/replication_info.cpp
@@ -36,8 +36,11 @@
#include "mongo/bson/util/bson_extract.h"
#include "mongo/client/connpool.h"
#include "mongo/client/dbclient_connection.h"
+#include "mongo/db/auth/sasl_command_constants.h"
+#include "mongo/db/auth/sasl_commands.h"
#include "mongo/db/auth/sasl_mechanism_registry.h"
#include "mongo/db/client.h"
+#include "mongo/db/commands/authentication_commands.h"
#include "mongo/db/commands/server_status.h"
#include "mongo/db/db_raii.h"
#include "mongo/db/dbhelpers.h"
@@ -516,6 +519,30 @@ public:
}
}
+ if (auto sae = cmdObj[auth::kSpeculativeAuthenticate]; !sae.eoo()) {
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "isMaster." << auth::kSpeculativeAuthenticate
+ << " must be an Object",
+ sae.type() == Object);
+ auto specAuth = sae.Obj();
+
+ uassert(ErrorCodes::BadValue,
+ str::stream() << "isMaster." << auth::kSpeculativeAuthenticate
+ << " must be a non-empty Object",
+ !specAuth.isEmpty());
+ auto specCmd = specAuth.firstElementFieldNameStringData();
+
+ if (specCmd == saslStartCommandName) {
+ doSpeculativeSaslStart(opCtx, specAuth, &result);
+ } else if (specCmd == auth::kAuthenticateCommand) {
+ doSpeculativeAuthenticate(opCtx, specAuth, &result);
+ } else {
+ uasserted(51769,
+ str::stream() << "isMaster." << auth::kSpeculativeAuthenticate
+ << " unknown command: " << specCmd);
+ }
+ }
+
return true;
}
} cmdismaster;
diff --git a/src/mongo/db/s/SConscript b/src/mongo/db/s/SConscript
index 976282cf4c3..d7bf39ae0d9 100644
--- a/src/mongo/db/s/SConscript
+++ b/src/mongo/db/s/SConscript
@@ -352,6 +352,7 @@ env.CppUnitTest(
'balancer/balancer_chunk_selection_policy_test.cpp',
'balancer/balancer_policy_test.cpp',
'balancer/cluster_statistics_test.cpp',
+ 'balancer/core_options_stub.cpp',
'balancer/migration_manager_test.cpp',
'balancer/migration_test_fixture.cpp',
'balancer/scoped_migration_request_test.cpp',
diff --git a/src/mongo/db/s/balancer/core_options_stub.cpp b/src/mongo/db/s/balancer/core_options_stub.cpp
new file mode 100644
index 00000000000..2b9c5d8b837
--- /dev/null
+++ b/src/mongo/db/s/balancer/core_options_stub.cpp
@@ -0,0 +1,42 @@
+/**
+ * Copyright (C) 2020-present MongoDB, Inc.
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the Server Side Public License, version 1,
+ * as published by MongoDB, Inc.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * Server Side Public License for more details.
+ *
+ * You should have received a copy of the Server Side Public License
+ * along with this program. If not, see
+ * <http://www.mongodb.com/licensing/server-side-public-license>.
+ *
+ * As a special exception, the copyright holders give permission to link the
+ * code of portions of this program with the OpenSSL library under certain
+ * conditions as described in each individual source file and distribute
+ * linked combinations including the program with the OpenSSL library. You
+ * must comply with the Server Side Public License in all respects for
+ * all of the code used other than as permitted herein. If you modify file(s)
+ * with this exception, you may extend this exception to your version of the
+ * file(s), but you are not obligated to do so. If you do not wish to do so,
+ * delete this exception statement from your version. If you delete this
+ * exception statement from all source files in the program, then also delete
+ * it in the license file.
+ */
+
+#include "mongo/base/init.h"
+#include "mongo/base/status.h"
+
+namespace mongo {
+namespace {
+MONGO_INITIALIZER_GENERAL(CoreOptions_Store,
+ ("BeginStartupOptionStorage"),
+ ("EndStartupOptionStorage"))
+(InitializerContext* context) {
+ return Status::OK();
+}
+} // namespace
+} // namespace mongo
diff --git a/src/mongo/db/stats/counters.cpp b/src/mongo/db/stats/counters.cpp
index 6cc06589d78..8e4458db7a7 100644
--- a/src/mongo/db/stats/counters.cpp
+++ b/src/mongo/db/stats/counters.cpp
@@ -33,6 +33,7 @@
#include "mongo/db/stats/counters.h"
+#include "mongo/client/authenticate.h"
#include "mongo/db/jsobj.h"
#include "mongo/util/log.h"
@@ -168,7 +169,65 @@ void NetworkCounter::append(BSONObjBuilder& b) {
b.append("tcpFastOpen", tfo.obj());
}
+void AuthCounter::initializeMechanismMap(const std::vector<std::string>& mechanisms) {
+ invariant(_mechanisms.empty());
+
+ for (const auto& mech : mechanisms) {
+ _mechanisms.emplace(
+ std::piecewise_construct, std::forward_as_tuple(mech), std::forward_as_tuple());
+ }
+}
+
+void AuthCounter::incSpeculativeAuthenticateReceived(const std::string& mechanism) try {
+ _mechanisms.at(mechanism).speculativeAuthenticate.received.fetchAndAddRelaxed(1);
+} catch (const std::out_of_range&) {
+ uasserted(51767,
+ str::stream() << "Received " << auth::kSpeculativeAuthenticate << " for mechanism "
+ << mechanism << " which is unknown or not enabled");
+}
+
+void AuthCounter::incSpeculativeAuthenticateSuccessful(const std::string& mechanism) try {
+ _mechanisms.at(mechanism).speculativeAuthenticate.successful.fetchAndAddRelaxed(1);
+} catch (const std::out_of_range&) {
+ // Should never actually occur since it'd mean we succeeded at a mechanism
+ // we're not configured for.
+ uasserted(51768,
+ str::stream() << "Unexpectedly succeeded at " << auth::kSpeculativeAuthenticate
+ << " for " << mechanism << " which is not enabled");
+}
+
+/**
+ * authentication: {
+ * "mechanisms": {
+ * "SCRAM-SHA-256": {
+ * "speculativeAuthenticate": { received: ###, successful: ### },
+ * },
+ * "MONGODB-X509": {
+ * "speculativeAuthenticate": { received: ###, successful: ### },
+ * },
+ * },
+ * }
+ */
+void AuthCounter::append(BSONObjBuilder* b) {
+ BSONObjBuilder mechsBuilder(b->subobjStart("mechanisms"));
+
+ for (const auto& it : _mechanisms) {
+ const auto received = it.second.speculativeAuthenticate.received.load();
+ const auto successful = it.second.speculativeAuthenticate.successful.load();
+
+ BSONObjBuilder mechBuilder(mechsBuilder.subobjStart(it.first));
+ BSONObjBuilder specAuthBuilder(mechBuilder.subobjStart(auth::kSpeculativeAuthenticate));
+ specAuthBuilder.append("received", received);
+ specAuthBuilder.append("successful", successful);
+ specAuthBuilder.done();
+ mechBuilder.done();
+ }
+
+ mechsBuilder.done();
+}
+
OpCounters globalOpCounters;
OpCounters replOpCounters;
NetworkCounter networkCounter;
+AuthCounter authCounter;
} // namespace mongo
diff --git a/src/mongo/db/stats/counters.h b/src/mongo/db/stats/counters.h
index 0afb3c6756c..edc17eee2bd 100644
--- a/src/mongo/db/stats/counters.h
+++ b/src/mongo/db/stats/counters.h
@@ -29,6 +29,8 @@
#pragma once
+#include <map>
+
#include "mongo/db/jsobj.h"
#include "mongo/platform/atomic_word.h"
#include "mongo/platform/basic.h"
@@ -165,4 +167,29 @@ private:
};
extern NetworkCounter networkCounter;
+
+class AuthCounter {
+public:
+ void incSpeculativeAuthenticateReceived(const std::string& mechanism);
+ void incSpeculativeAuthenticateSuccessful(const std::string& mechanism);
+
+ void append(BSONObjBuilder*);
+
+ void initializeMechanismMap(const std::vector<std::string>&);
+
+private:
+ struct MechanismData {
+ struct {
+ AtomicWord<long long> received;
+ AtomicWord<long long> successful;
+ } speculativeAuthenticate;
+ };
+ using MechanismMap = std::map<std::string, MechanismData>;
+
+ // Mechanism maps are initialized at startup to contain all
+ // mechanisms known to authenticationMechanisms setParam.
+ // After that they are kept to a fixed size.
+ MechanismMap _mechanisms;
+};
+extern AuthCounter authCounter;
} // namespace mongo
diff --git a/src/mongo/executor/connection_pool_tl.cpp b/src/mongo/executor/connection_pool_tl.cpp
index c3816eab43c..6d3150d1218 100644
--- a/src/mongo/executor/connection_pool_tl.cpp
+++ b/src/mongo/executor/connection_pool_tl.cpp
@@ -157,6 +157,7 @@ public:
if (internalSecurity.user) {
bob.append("saslSupportedMechs", internalSecurity.user->getName().getUnambiguousName());
}
+ _speculativeAuthType = auth::speculateInternalAuth(&bob, &_session);
return bob.obj();
}
@@ -164,7 +165,9 @@ public:
Status validateHost(const HostAndPort& remoteHost,
const BSONObj& isMasterRequest,
const RemoteCommandResponse& isMasterReply) override try {
- const auto saslMechsElem = isMasterReply.data.getField("saslSupportedMechs");
+ const auto& reply = isMasterReply.data;
+
+ const auto saslMechsElem = reply.getField("saslSupportedMechs");
if (saslMechsElem.type() == Array) {
auto array = saslMechsElem.Array();
for (const auto& elem : array) {
@@ -172,6 +175,11 @@ public:
}
}
+ const auto specAuth = reply.getField(auth::kSpeculativeAuthenticate);
+ if (specAuth.type() == Object) {
+ _speculativeAuthenticate = specAuth.Obj().getOwned();
+ }
+
if (!_wrappedHook) {
return Status::OK();
} else {
@@ -202,8 +210,23 @@ public:
return _saslMechsForInternalAuth;
}
+ std::shared_ptr<SaslClientSession> getSession() {
+ return _session;
+ }
+
+ auth::SpeculativeAuthType getSpeculativeAuthType() const {
+ return _speculativeAuthType;
+ }
+
+ BSONObj getSpeculativeAuthenticateReply() {
+ return _speculativeAuthenticate;
+ }
+
private:
std::vector<std::string> _saslMechsForInternalAuth;
+ std::shared_ptr<SaslClientSession> _session;
+ auth::SpeculativeAuthType _speculativeAuthType;
+ BSONObj _speculativeAuthenticate;
executor::NetworkConnectionHook* const _wrappedHook = nullptr;
};
@@ -240,8 +263,18 @@ void TLConnection::setup(Milliseconds timeout, SetupCallback cb) {
_client = std::move(client);
return _client->initWireVersion("NetworkInterfaceTL", isMasterHook.get());
})
- .then([this, isMasterHook] {
+ .then([this, isMasterHook]() -> Future<bool> {
if (_skipAuth) {
+ return false;
+ }
+
+ return _client->completeSpeculativeAuth(isMasterHook->getSession(),
+ auth::getInternalAuthDB(),
+ isMasterHook->getSpeculativeAuthenticateReply(),
+ isMasterHook->getSpeculativeAuthType());
+ })
+ .then([this, isMasterHook](bool authenticatedDuringConnect) {
+ if (_skipAuth || authenticatedDuringConnect) {
return Future<void>::makeReady();
}
diff --git a/src/mongo/util/net/ssl_manager.cpp b/src/mongo/util/net/ssl_manager.cpp
index 6831d4c9daa..d0b6ae3988f 100644
--- a/src/mongo/util/net/ssl_manager.cpp
+++ b/src/mongo/util/net/ssl_manager.cpp
@@ -692,12 +692,10 @@ bool SSLConfiguration::isClusterMember(StringData subjectName) const {
return !canonicalClient.empty() && (canonicalClient == _canonicalServerSubjectName);
}
-BSONObj SSLConfiguration::getServerStatusBSON() const {
- BSONObjBuilder security;
- security.append("SSLServerSubjectName", _serverSubjectName.toString());
- security.appendBool("SSLServerHasCertificateAuthority", hasCA);
- security.appendDate("SSLServerCertificateExpirationDate", serverCertificateExpirationDate);
- return security.obj();
+void SSLConfiguration::getServerStatusBSON(BSONObjBuilder* security) const {
+ security->append("SSLServerSubjectName", _serverSubjectName.toString());
+ security->appendBool("SSLServerHasCertificateAuthority", hasCA);
+ security->appendDate("SSLServerCertificateExpirationDate", serverCertificateExpirationDate);
}
SSLManagerInterface::~SSLManagerInterface() {}
diff --git a/src/mongo/util/net/ssl_manager.h b/src/mongo/util/net/ssl_manager.h
index 499cdd85b7e..37a0bdf72b1 100644
--- a/src/mongo/util/net/ssl_manager.h
+++ b/src/mongo/util/net/ssl_manager.h
@@ -119,7 +119,7 @@ class SSLConfiguration {
public:
bool isClusterMember(StringData subjectName) const;
bool isClusterMember(SSLX509Name subjectName) const;
- BSONObj getServerStatusBSON() const;
+ void getServerStatusBSON(BSONObjBuilder*) const;
Status setServerSubjectName(SSLX509Name name);
const SSLX509Name& serverSubjectName() const {