summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShreyas Kalyan <shreyas.kalyan@10gen.com>2019-02-21 09:31:17 -0500
committerShreyas Kalyan <shreyas.kalyan@10gen.com>2019-03-29 10:57:29 -0400
commite02f09280a43bcbc1c7011d480958d7f92da44f0 (patch)
treeaffd34b9c45faf2dfd33f11b0f625cd73ab3be58
parent9b00696ed75f65e1ebc8d635593bed79b290cfbb (diff)
downloadmongo-e02f09280a43bcbc1c7011d480958d7f92da44f0.tar.gz
SERVER-39178 Negotiate SCRAM mechanism in MongoURI::connect()
(cherry picked from commit 6f083bd87264e9d9c3d637fae62103c36a65316a)
-rw-r--r--jstests/auth/mongoURIAuth.js72
-rw-r--r--src/mongo/client/authenticate.cpp1
-rw-r--r--src/mongo/client/authenticate.h2
-rw-r--r--src/mongo/client/dbclient_connection.cpp21
-rw-r--r--src/mongo/client/dbclientinterface.h6
-rw-r--r--src/mongo/client/mongo_uri.h5
-rw-r--r--src/mongo/client/mongo_uri_connect.cpp20
-rw-r--r--src/mongo/db/auth/user_name.h8
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 {