diff options
author | Shreyas Kalyan <shreyas.kalyan@10gen.com> | 2019-02-21 09:31:17 -0500 |
---|---|---|
committer | Shreyas Kalyan <shreyas.kalyan@10gen.com> | 2019-03-29 10:57:29 -0400 |
commit | e02f09280a43bcbc1c7011d480958d7f92da44f0 (patch) | |
tree | affd34b9c45faf2dfd33f11b0f625cd73ab3be58 | |
parent | 9b00696ed75f65e1ebc8d635593bed79b290cfbb (diff) | |
download | mongo-e02f09280a43bcbc1c7011d480958d7f92da44f0.tar.gz |
SERVER-39178 Negotiate SCRAM mechanism in MongoURI::connect()
(cherry picked from commit 6f083bd87264e9d9c3d637fae62103c36a65316a)
-rw-r--r-- | jstests/auth/mongoURIAuth.js | 72 | ||||
-rw-r--r-- | src/mongo/client/authenticate.cpp | 1 | ||||
-rw-r--r-- | src/mongo/client/authenticate.h | 2 | ||||
-rw-r--r-- | src/mongo/client/dbclient_connection.cpp | 21 | ||||
-rw-r--r-- | src/mongo/client/dbclientinterface.h | 6 | ||||
-rw-r--r-- | src/mongo/client/mongo_uri.h | 5 | ||||
-rw-r--r-- | src/mongo/client/mongo_uri_connect.cpp | 20 | ||||
-rw-r--r-- | src/mongo/db/auth/user_name.h | 8 |
8 files changed, 125 insertions, 10 deletions
diff --git a/jstests/auth/mongoURIAuth.js b/jstests/auth/mongoURIAuth.js new file mode 100644 index 00000000000..7f60b2e750e --- /dev/null +++ b/jstests/auth/mongoURIAuth.js @@ -0,0 +1,72 @@ +// This tests that the shell successfully breaks down the URI and authenticates using +// the specified auth mechanism. + +(function() { + 'use strict'; + + const runURIAuthTest = function(userMech, uriMech, authMechanism, regexMechanism) { + const conn = MongoRunner.runMongod({auth: ""}); + const adminDB = conn.getDB("admin"); + + adminDB.createUser({ + user: "u", + pwd: "p", + roles: ["root"], + + }); + adminDB.auth("u", "p"); + adminDB.setLogLevel(2, "command"); + + if (userMech) { + adminDB.createUser({ + user: "user", + pwd: "password", + roles: ["root"], + mechanisms: [authMechanism], + }); + } else { + adminDB.createUser({ + user: "user", + pwd: "password", + roles: ["root"], + }); + } + + var uri; + + if (uriMech) { + uri = "mongodb://user:password@localhost:" + conn.port + "/admin?authMechanism=" + + authMechanism; + } else { + uri = "mongodb://user:password@localhost:" + conn.port; + } + + var shell = runMongoProgram('./mongo', uri, "--eval", "db.getName()"); + assert.eq(shell, 0, "Should be able to connect with specified params."); + + const log = adminDB.runCommand({getLog: "global"}); + adminDB.logout(); + const matches = tojson(log.log).match(regexMechanism); + assert(matches); + assert.eq(2, matches.length); + + MongoRunner.stopMongod(conn); + }; + + const SCRAM_SHA_256 = "SCRAM-SHA-256"; + const SCRAM_SHA_1 = "SCRAM-SHA-1"; + + const SCRAM_SHA_256_regex = /saslStart.*mechanism:.*SCRAM-SHA-256/g; + const SCRAM_SHA_1_regex = /saslStart.*mechanism:.*SCRAM-SHA-1/g; + + jsTestLog("Test that a mechanism specified in the URI is the chosen authentication method."); + runURIAuthTest(false, true, SCRAM_SHA_256, SCRAM_SHA_256_regex); + + jsTestLog( + "Test that a mechanism specified in CreateUser() is the chosen authentication method."); + runURIAuthTest(true, false, SCRAM_SHA_1, SCRAM_SHA_1_regex); + + jsTestLog("Test that SCRAM-SHA-1 is the default authentication method."); + runURIAuthTest(false, false, SCRAM_SHA_256, SCRAM_SHA_256_regex); + +})();
\ No newline at end of file diff --git a/src/mongo/client/authenticate.cpp b/src/mongo/client/authenticate.cpp index dd58bec015a..67dde48fbd9 100644 --- a/src/mongo/client/authenticate.cpp +++ b/src/mongo/client/authenticate.cpp @@ -61,6 +61,7 @@ const char* const kMechanismMongoX509 = "MONGODB-X509"; const char* const kMechanismSaslPlain = "PLAIN"; const char* const kMechanismGSSAPI = "GSSAPI"; const char* const kMechanismScramSha1 = "SCRAM-SHA-1"; +const char* const kMechanismScramSha256 = "SCRAM-SHA-256"; namespace { diff --git a/src/mongo/client/authenticate.h b/src/mongo/client/authenticate.h index 846b2659fc6..d90c8a5d77c 100644 --- a/src/mongo/client/authenticate.h +++ b/src/mongo/client/authenticate.h @@ -60,10 +60,12 @@ extern AuthMongoCRHandler authMongoCR; * Names for supported authentication mechanisms. */ +extern const char* const kMechanismMongoCR; extern const char* const kMechanismMongoX509; extern const char* const kMechanismSaslPlain; extern const char* const kMechanismGSSAPI; extern const char* const kMechanismScramSha1; +extern const char* const kMechanismScramSha256; /** * Authenticate a user. diff --git a/src/mongo/client/dbclient_connection.cpp b/src/mongo/client/dbclient_connection.cpp index 67137931f39..d17bc45d12d 100644 --- a/src/mongo/client/dbclient_connection.cpp +++ b/src/mongo/client/dbclient_connection.cpp @@ -49,6 +49,7 @@ #include "mongo/client/replica_set_monitor.h" #include "mongo/config.h" #include "mongo/db/auth/internal_user_auth.h" +#include "mongo/db/auth/user_name.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" #include "mongo/db/commands/test_commands_enabled.h" @@ -113,7 +114,9 @@ private: * Initializes the wire version of conn, and returns the isMaster reply. */ executor::RemoteCommandResponse initWireVersion(DBClientConnection* conn, - StringData applicationName) { + 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_COMMAND on a connection. This is necessary to handle the case @@ -123,6 +126,12 @@ executor::RemoteCommandResponse initWireVersion(DBClientConnection* 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()); + } + 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 @@ -158,6 +167,14 @@ executor::RemoteCommandResponse initWireVersion(DBClientConnection* conn, 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); return executor::RemoteCommandResponse{ @@ -206,7 +223,7 @@ 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); + auto swIsMasterReply = initWireVersion(this, _applicationName, _uri, &_saslMechsForAuth); if (!swIsMasterReply.isOK()) { _markFailed(kSetFlag); return swIsMasterReply.status; diff --git a/src/mongo/client/dbclientinterface.h b/src/mongo/client/dbclientinterface.h index 841b9abf1da..3d54a4c4171 100644 --- a/src/mongo/client/dbclientinterface.h +++ b/src/mongo/client/dbclientinterface.h @@ -707,6 +707,10 @@ public: virtual int getMinWireVersion() = 0; virtual int getMaxWireVersion() = 0; + const std::vector<std::string>& getIsMasterSaslMechanisms() const { + return _saslMechsForAuth; + } + /** send a query to the database. @param ns namespace to query, format is <dbname>.<collectname>[.<collectname>]* @param query query to perform on the collection. this is a BSONObj (binary JSON) @@ -838,6 +842,8 @@ protected: // should be set by subclasses during connection. void _setServerRPCProtocols(rpc::ProtocolSet serverProtocols); + std::vector<std::string> _saslMechsForAuth; + /** controls how chatty the client is about network errors & such. See log.h */ const logger::LogSeverity _logLevel; diff --git a/src/mongo/client/mongo_uri.h b/src/mongo/client/mongo_uri.h index e600522b463..31341a0e9ee 100644 --- a/src/mongo/client/mongo_uri.h +++ b/src/mongo/client/mongo_uri.h @@ -166,7 +166,7 @@ public: return _database; } - std::string getAuthenticationDatabase() { + std::string getAuthenticationDatabase() const { auto authDB = _options.find("authSource"); if (authDB != _options.end()) { return authDB->second; @@ -249,7 +249,8 @@ private: _sslMode(sslMode), _options(std::move(options)) {} - boost::optional<BSONObj> _makeAuthObjFromOptions(int maxWireVersion) const; + boost::optional<BSONObj> _makeAuthObjFromOptions( + int maxWireVersion, const std::vector<std::string>& saslMechsForAuth) const; static MongoURI parseImpl(const std::string& url); diff --git a/src/mongo/client/mongo_uri_connect.cpp b/src/mongo/client/mongo_uri_connect.cpp index 7057116f904..2b2ec362c41 100644 --- a/src/mongo/client/mongo_uri_connect.cpp +++ b/src/mongo/client/mongo_uri_connect.cpp @@ -56,8 +56,6 @@ const char kAuthMechanismPropertiesKey[] = "mechanism_properties"; const char kAuthServiceName[] = "SERVICE_NAME"; const char kAuthServiceRealm[] = "SERVICE_REALM"; -const char kAuthMechMongoCR[] = "MONGODB-CR"; -const char kAuthMechScramSha1[] = "SCRAM-SHA-1"; const char kAuthMechDefault[] = "DEFAULT"; const char* const kSupportedAuthMechanismProperties[] = {kAuthServiceName, kAuthServiceRealm}; @@ -102,7 +100,8 @@ std::string authKeyCopyDBMongoCR(const std::string& username, } // namespace -boost::optional<BSONObj> MongoURI::_makeAuthObjFromOptions(int maxWireVersion) const { +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. @@ -128,10 +127,18 @@ boost::optional<BSONObj> MongoURI::_makeAuthObjFromOptions(int maxWireVersion) c if (it->second == auth::kMechanismMongoX509) { 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, kAuthMechScramSha1); + bob.append(saslCommandMechanismFieldName, auth::kMechanismScramSha1); } else { - bob.append(saslCommandMechanismFieldName, kAuthMechMongoCR); + bob.append(saslCommandMechanismFieldName, auth::kMechanismMongoCR); } if (usernameRequired && _user.empty()) { @@ -200,7 +207,8 @@ DBClientBase* MongoURI::connect(StringData applicationName, return nullptr; } - auto optAuthObj = _makeAuthObjFromOptions(ret->getMaxWireVersion()); + auto optAuthObj = + _makeAuthObjFromOptions(ret->getMaxWireVersion(), ret->getIsMasterSaslMechanisms()); if (optAuthObj) { ret->auth(optAuthObj.get()); } diff --git a/src/mongo/db/auth/user_name.h b/src/mongo/db/auth/user_name.h index e34e5054f2d..e3c8b0f6bc3 100644 --- a/src/mongo/db/auth/user_name.h +++ b/src/mongo/db/auth/user_name.h @@ -39,6 +39,7 @@ #include "mongo/base/disallow_copying.h" #include "mongo/base/status_with.h" #include "mongo/base/string_data.h" +#include "mongo/util/mongoutils/str.h" namespace mongo { @@ -79,6 +80,13 @@ public: } /** + * Gets the full unambiguous unique name of a user as a string, formatted as "db.user" + */ + std::string getUnambiguousName() const { + return str::stream() << getDB() << "." << getUser(); + } + + /** * Stringifies the object, for logging/debugging. */ std::string toString() const { |