diff options
-rw-r--r-- | src/mongo/client/SConscript | 103 | ||||
-rw-r--r-- | src/mongo/client/authenticate.cpp | 248 | ||||
-rw-r--r-- | src/mongo/client/authenticate.h | 108 | ||||
-rw-r--r-- | src/mongo/client/authenticate_test.cpp | 177 | ||||
-rw-r--r-- | src/mongo/client/dbclient.cpp | 183 | ||||
-rw-r--r-- | src/mongo/client/dbclientinterface.h | 18 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate.cpp | 3 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate.h | 13 | ||||
-rw-r--r-- | src/mongo/client/sasl_client_authenticate_impl.cpp | 32 | ||||
-rw-r--r-- | src/mongo/db/auth/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/internal_user_auth.cpp | 7 | ||||
-rw-r--r-- | src/mongo/db/auth/internal_user_auth.h | 5 | ||||
-rw-r--r-- | src/mongo/db/commands/parameters.cpp | 1 | ||||
-rw-r--r-- | src/mongo/db/initialize_server_global_state.cpp | 1 | ||||
-rw-r--r-- | src/mongo/executor/SConscript | 13 |
15 files changed, 686 insertions, 227 deletions
diff --git a/src/mongo/client/SConscript b/src/mongo/client/SConscript index 1fd8ab6a7ac..f7c0b7931df 100644 --- a/src/mongo/client/SConscript +++ b/src/mongo/client/SConscript @@ -48,36 +48,90 @@ env.CppUnitTest( ] ) +saslClientEnv = env.Clone() +saslLibs = [] +saslClientSource = [ + 'native_sasl_client_session.cpp', + 'sasl_client_authenticate.cpp', + 'sasl_client_authenticate_impl.cpp', + 'sasl_client_conversation.cpp', + 'sasl_client_session.cpp', + 'sasl_plain_client_conversation.cpp', + 'sasl_scramsha1_client_conversation.cpp', +] + +# Add in actual sasl dependencies if sasl is enabled, otherwise +# leave library empty so other targets can link to it unconditionally +# without needing to first test MONGO_BUILD_SASL_CLIENT. +if env['MONGO_BUILD_SASL_CLIENT']: + saslClientSource.extend([ + 'cyrus_sasl_client_session.cpp', + 'sasl_sspi.cpp', + ]) + + saslLibs.extend(['sasl2']) + if env.TargetOSIs('windows'): + saslLibs.extend(['secur32']) + +saslClientEnv.Library( + target='sasl_client', + source=saslClientSource, + LIBDEPS=[ + '$BUILD_DIR/mongo/crypto/scramauth', + '$BUILD_DIR/mongo/rpc/command_status', + '$BUILD_DIR/mongo/util/foundation', + ], + SYSLIBDEPS=saslLibs +) + +env.Library( + target='authentication', + source=[ + 'authenticate.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/bson/util/bson_extract', + '$BUILD_DIR/mongo/executor/remote_command', + 'sasl_client' + ] +) + +env.CppUnitTest( + target=[ + 'authenticate_test', + ], + source=[ + 'authenticate_test.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/rpc/command_status', + '$BUILD_DIR/mongo/util/net/hostandport', + '$BUILD_DIR/mongo/util/md5', + 'authentication', + ] +) + env.Library( target='clientdriver', source=[ + '$BUILD_DIR/mongo/db/dbmessage.cpp', 'connection_string_connect.cpp', 'connpool.cpp', 'dbclient.cpp', 'dbclient_rs.cpp', 'dbclientcursor.cpp', 'global_conn_pool.cpp', - 'native_sasl_client_session.cpp', 'replica_set_monitor.cpp', 'replica_set_monitor_manager.cpp', - 'sasl_client_authenticate.cpp', - 'sasl_client_authenticate_impl.cpp', - 'sasl_client_conversation.cpp', - 'sasl_client_session.cpp', - 'sasl_plain_client_conversation.cpp', - 'sasl_scramsha1_client_conversation.cpp', 'syncclusterconnection.cpp', ], LIBDEPS=[ - 'connection_string', - '$BUILD_DIR/mongo/bson/util/bson_extract', - '$BUILD_DIR/mongo/crypto/scramauth', - '$BUILD_DIR/mongo/db/auth/authcommon', '$BUILD_DIR/mongo/rpc/command_status', '$BUILD_DIR/mongo/rpc/rpc', '$BUILD_DIR/mongo/util/net/network', '$BUILD_DIR/mongo/util/md5', - 'cyrus_sasl_client_session', + 'authentication', + 'connection_string', 'read_preference', ] ) @@ -150,31 +204,6 @@ env.CppUnitTest('dbclient_rs_test', ['dbclient_rs_test.cpp'], LIBDEPS=['clientdriver', '$BUILD_DIR/mongo/dbtests/mocklib']) -if env['MONGO_BUILD_SASL_CLIENT']: - saslLibs = ['sasl2'] - if env.TargetOSIs('windows'): - saslLibs.extend(['secur32']) - - env.Library( - target='cyrus_sasl_client_session', - source=[ - 'cyrus_sasl_client_session.cpp', - 'sasl_sspi.cpp', - ], - LIBDEPS=[ - '$BUILD_DIR/mongo/util/foundation', - ], - SYSLIBDEPS=saslLibs, - ) -else: - # Create a dummy sasl client library so that other targets can unconditionally - # link to it, without needing to first test MONGO_BUILD_SASL_CLIENT. - env.Library( - target='cyrus_sasl_client_session', - source=[], - LIBDEPS=[] - ) - env.CppUnitTest( target='scoped_db_conn_test', source=[ diff --git a/src/mongo/client/authenticate.cpp b/src/mongo/client/authenticate.cpp new file mode 100644 index 00000000000..ce52b336356 --- /dev/null +++ b/src/mongo/client/authenticate.cpp @@ -0,0 +1,248 @@ +/* Copyright 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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. + */ + +#define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl + +#include "mongo/platform/basic.h" + +#include "mongo/client/authenticate.h" + +#include "mongo/bson/json.h" +#include "mongo/base/status.h" +#include "mongo/base/status_with.h" +#include "mongo/bson/util/bson_extract.h" +#include "mongo/client/sasl_client_authenticate.h" +#include "mongo/config.h" +#include "mongo/rpc/get_status_from_command_result.h" +#include "mongo/util/net/ssl_manager.h" +#include "mongo/util/net/ssl_options.h" +#include "mongo/util/password_digest.h" + +namespace mongo { +namespace auth { + +using executor::RemoteCommandRequest; +using executor::RemoteCommandResponse; + +namespace { + +// TODO: These constants need to be cleaned up. +const char* const saslCommandUserSourceFieldName = "userSource"; +const BSONObj getnoncecmdobj = fromjson("{getnonce:1}"); + +bool isOk(const BSONObj& o) { + return getStatusFromCommandResult(o).isOK(); +} + +BSONObj getFallbackAuthParams(const BSONObj& params) { + if (params["fallbackParams"].type() != Object) { + return BSONObj(); + } + return params["fallbackParams"].Obj(); +} + +// Use the MONGODB-CR protocol to authenticate as "username" against the database "dbname", +// with the given password. If digestPassword is false, the password is assumed to be +// pre-digested. Returns false on failure, and sets "errmsg". +bool authMongoCR(RunCommandHook runCommand, + StringData dbname, + StringData username, + StringData password_text, + BSONObj* info, + bool digestPassword) { + auto request = RemoteCommandRequest(); + request.cmdObj = getnoncecmdobj; + request.dbname = dbname.toString(); + + std::string password = password_text.toString(); + if (digestPassword) + password = createPasswordDigest(username, password_text); + + std::string nonce; + invariant(info != nullptr && runCommand); + auto runCommandHandler = [&info](StatusWith<RemoteCommandResponse> response) { + *info = response.getValue().data.getOwned(); + }; + + runCommand(request, runCommandHandler); + if (!isOk(*info)) { + return false; + } + + { + BSONElement e = info->getField("nonce"); + verify(e.type() == String); + nonce = e.valuestr(); + } + + BSONObjBuilder b; + { + b << "authenticate" << 1 << "nonce" << nonce << "user" << username; + md5digest d; + { + md5_state_t st; + md5_init(&st); + md5_append(&st, (const md5_byte_t*)nonce.c_str(), nonce.size()); + md5_append(&st, (const md5_byte_t*)username.rawData(), username.size()); + md5_append(&st, (const md5_byte_t*)password.c_str(), password.size()); + md5_finish(&st, d); + } + b << "key" << digestToString(d); + request.cmdObj = b.done(); + } + + runCommand(request, runCommandHandler); + return isOk(*info); +} + +// Use the MONGODB-X509 protocol to authenticate as "username." The certificate details +// has already been communicated automatically as part of the connect call. +// Returns false on failure and set "errmsg". +bool authX509(RunCommandHook runCommand, StringData dbname, StringData username, BSONObj* info) { + BSONObjBuilder cmdBuilder; + cmdBuilder << "authenticate" << 1 << "mechanism" + << "MONGODB-X509" + << "user" << username; + + auto request = RemoteCommandRequest(); + request.dbname = dbname.toString(); + request.cmdObj = cmdBuilder.done(); + + runCommand(request, + [&info](StatusWith<RemoteCommandResponse> response) { + *info = response.getValue().data.getOwned(); + }); + + return isOk(*info); +} + +void auth(RunCommandHook runCommand, + const BSONObj& params, + StringData hostname, + StringData clientName) { + std::string mechanism; + + uassertStatusOK(bsonExtractStringField(params, saslCommandMechanismFieldName, &mechanism)); + + uassert(17232, + "You cannot specify both 'db' and 'userSource'. Please use only 'db'.", + !(params.hasField(saslCommandUserDBFieldName) && + params.hasField(saslCommandUserSourceFieldName))); + + if (mechanism == StringData("MONGODB-CR", StringData::LiteralTag())) { + std::string db; + if (params.hasField(saslCommandUserSourceFieldName)) { + uassertStatusOK(bsonExtractStringField(params, saslCommandUserSourceFieldName, &db)); + } else { + uassertStatusOK(bsonExtractStringField(params, saslCommandUserDBFieldName, &db)); + } + std::string user; + uassertStatusOK(bsonExtractStringField(params, saslCommandUserFieldName, &user)); + std::string password; + uassertStatusOK(bsonExtractStringField(params, saslCommandPasswordFieldName, &password)); + bool digestPassword; + uassertStatusOK(bsonExtractBooleanFieldWithDefault( + params, saslCommandDigestPasswordFieldName, true, &digestPassword)); + BSONObj result; + uassert(result["code"].Int(), + result.toString(), + authMongoCR(runCommand, db, user, password, &result, digestPassword)); + } +#ifdef MONGO_CONFIG_SSL + else if (mechanism == StringData("MONGODB-X509", StringData::LiteralTag())) { + std::string db; + if (params.hasField(saslCommandUserSourceFieldName)) { + uassertStatusOK(bsonExtractStringField(params, saslCommandUserSourceFieldName, &db)); + } else { + uassertStatusOK(bsonExtractStringField(params, saslCommandUserDBFieldName, &db)); + } + std::string user; + uassertStatusOK(bsonExtractStringField(params, saslCommandUserFieldName, &user)); + + uassert(ErrorCodes::AuthenticationFailed, + "Please enable SSL on the client-side to use the MONGODB-X509 " + "authentication mechanism.", + clientName.toString() != ""); + + uassert(ErrorCodes::AuthenticationFailed, + "Username \"" + user + "\" does not match the provided client certificate user \"" + + clientName.toString() + "\"", + user == clientName.toString()); + + BSONObj result; + uassert(result["code"].Int(), result.toString(), authX509(runCommand, db, user, &result)); + } +#endif + else if (saslClientAuthenticate != nullptr) { + uassertStatusOK(saslClientAuthenticate(runCommand, hostname, params)); + } else { + uasserted(ErrorCodes::BadValue, + mechanism + " mechanism support not compiled into client library."); + } +}; + +} // namespace + +void authenticateClient(const BSONObj& params, + StringData hostname, + StringData clientName, + RunCommandHook runCommand) { + try { + auth(runCommand, params, hostname, clientName); + return; + } catch (const UserException& ex) { + if (getFallbackAuthParams(params).isEmpty() || + (ex.getCode() != ErrorCodes::BadValue && ex.getCode() != ErrorCodes::CommandNotFound)) { + throw ex; + } + } + + // BadValue or CommandNotFound indicates unsupported auth mechanism so fall back to + // MONGODB-CR for 2.6 compatibility. + auth(runCommand, getFallbackAuthParams(params), hostname, clientName); +} + +BSONObj buildAuthParams(StringData dbname, + StringData username, + StringData passwordText, + bool digestPassword) { + return BSON(saslCommandMechanismFieldName + << "SCRAM-SHA-1" << saslCommandUserDBFieldName << dbname << saslCommandUserFieldName + << username << saslCommandPasswordFieldName << passwordText + << saslCommandDigestPasswordFieldName << digestPassword); +} + +StringData getSaslCommandUserDBFieldName() { + return saslCommandUserDBFieldName; +} + +StringData getSaslCommandUserFieldName() { + return saslCommandUserFieldName; +} + +} // namespace auth +} // namespace mongo diff --git a/src/mongo/client/authenticate.h b/src/mongo/client/authenticate.h new file mode 100644 index 00000000000..7a5a40f6075 --- /dev/null +++ b/src/mongo/client/authenticate.h @@ -0,0 +1,108 @@ +/* Copyright 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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 <string> + +#include "mongo/base/status_with.h" +#include "mongo/base/string_data.h" +#include "mongo/executor/remote_command_request.h" +#include "mongo/executor/remote_command_response.h" +#include "mongo/stdx/functional.h" +#include "mongo/util/md5.h" + +namespace mongo { + +class DBClientWithCommands; +class BSONObj; + +namespace auth { + +using RunCommandResultHandler = stdx::function<void(StatusWith<executor::RemoteCommandResponse>)>; +using RunCommandHook = + stdx::function<void(executor::RemoteCommandRequest, RunCommandResultHandler)>; + +/** + * Authenticate a user. + * + * Pass the default hostname for this client in through "hostname." If SSL is enabled and + * there is a stored client subject name, pass that through the "clientSubjectName" parameter. + * Otherwise, "clientSubjectName" will be ignored, pass in any string. + * + * The "params" BSONObj should be initialized with some of the fields below. Which fields + * are required depends on the mechanism, which is mandatory. + * + * "mechanism": The std::string name of the sasl mechanism to use. Mandatory. + * "user": The std::string name of the user to authenticate. Mandatory. + * "db": The database target of the auth command, which identifies the location + * of the credential information for the user. May be "$external" if + * credential information is stored outside of the mongo cluster. Mandatory. + * "pwd": The password data. + * "digestPassword": Boolean, set to true if the "pwd" is undigested (default). + * "serviceName": The GSSAPI service name to use. Defaults to "mongodb". + * "serviceHostname": The GSSAPI hostname to use. Defaults to the name of the remote + * host. + * + * Other fields in "params" are silently ignored. A "params" object can be constructed + * using the buildAuthParams() method. + * + * Returns normally on success, and throws on error. Throws a DBException with getCode() == + * ErrorCodes::AuthenticationFailed if authentication is rejected. All other exceptions are + * tantamount to authentication failure, but may also indicate more serious problems. + */ +void authenticateClient(const BSONObj& params, + StringData hostname, + StringData clientSubjectName, + RunCommandHook runCommand); + +/** + * Build a BSONObject representing parameters to be passed to authenticateClient(). Takes + * the following fields: + * + * @dbname: The database target of the auth command. + * @username: The std::string name of the user to authenticate. + * @passwordText: The std::string representing the user's password. + * @digestPassword: Set to true if the password is undigested. + */ +BSONObj buildAuthParams(StringData dbname, + StringData username, + StringData passwordText, + bool digestPassword); + +/** + * Return the field name for the database containing credential information. + */ +StringData getSaslCommandUserDBFieldName(); + +/** + * Return the field name for the user to authenticate. + */ +StringData getSaslCommandUserFieldName(); + +} // namespace auth +} // namespace mongo diff --git a/src/mongo/client/authenticate_test.cpp b/src/mongo/client/authenticate_test.cpp new file mode 100644 index 00000000000..75a12c11e2d --- /dev/null +++ b/src/mongo/client/authenticate_test.cpp @@ -0,0 +1,177 @@ +/** + * Copyright (C) 2015 MongoDB Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License, version 3, + * as published by the Free Software Foundation. + * + * 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 + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + * + * 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 GNU Affero General 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/platform/basic.h" + +#include <queue> + +#include "mongo/bson/util/bson_extract.h" +#include "mongo/client/authenticate.h" +#include "mongo/config.h" +#include "mongo/db/jsobj.h" +#include "mongo/unittest/unittest.h" +#include "mongo/util/base64.h" +#include "mongo/util/md5.hpp" +#include "mongo/util/net/hostandport.h" +#include "mongo/util/password_digest.h" + +namespace { + +using namespace mongo; +using executor::RemoteCommandRequest; +using executor::RemoteCommandResponse; + +using auth::RunCommandResultHandler; + +/** + * Utility class to support tests in this file. Allows caller to load + * with pre-made responses and requests to interject into authentication methods. + */ +class AuthClientTest : public mongo::unittest::Test { +public: + AuthClientTest() + : _mockHost(), + _millis(100), + _username("PinkPanther"), + _password("shhhhhhh"), + _password_digest(createPasswordDigest(_username, _password)), + _nonce("7ca422a24f326f2a"), + _requests(), + _responses() { + _runCommandCallback = + [this](RemoteCommandRequest request, RunCommandResultHandler handler) { + runCommand(request, handler); + }; + + // create our digest + md5digest d; + { + md5_state_t st; + md5_init(&st); + md5_append(&st, (const md5_byte_t*)_nonce.c_str(), _nonce.size()); + md5_append(&st, (const md5_byte_t*)_username.c_str(), _username.size()); + md5_append(&st, (const md5_byte_t*)_password_digest.c_str(), _password_digest.size()); + md5_finish(&st, d); + } + _digest = digestToString(d); + } + + // protected: + void runCommand(RemoteCommandRequest request, RunCommandResultHandler handler) { + // Validate the received request + ASSERT(!_requests.empty()); + RemoteCommandRequest expected = _requests.front(); + + ASSERT(expected.dbname == request.dbname); + ASSERT_EQ(expected.cmdObj, request.cmdObj); + _requests.pop(); + + // Then pop a response and call the handler + ASSERT(!_responses.empty()); + handler(StatusWith<RemoteCommandResponse>(_responses.front())); + _responses.pop(); + } + + void reset() { + // If there are things left then we did something wrong. + ASSERT(_responses.empty()); + ASSERT(_requests.empty()); + } + + void pushResponse(const BSONObj& cmd) { + _responses.emplace(cmd, BSONObj(), _millis); + } + + void pushRequest(StringData dbname, const BSONObj& cmd) { + _requests.emplace(_mockHost, dbname.toString(), cmd); + } + + auth::RunCommandHook _runCommandCallback; + + // Auth code doesn't use HostAndPort information. + HostAndPort _mockHost; + Milliseconds _millis; + + // Some credentials + std::string _username; + std::string _password; + std::string _password_digest; + std::string _digest; + std::string _nonce; + + std::queue<RemoteCommandRequest> _requests; + std::queue<RemoteCommandResponse> _responses; +}; + +TEST_F(AuthClientTest, MongoCR) { + // 1. Client sends 'getnonce' command + pushRequest("admin", BSON("getnonce" << 1)); + + // 2. Client receives nonce + pushResponse(BSON("nonce" << _nonce << "ok" << 1)); + + // 3. Client sends 'authenticate' command + pushRequest( + "admin", + BSON("authenticate" << 1 << "nonce" << _nonce << "user" << _username << "key" << _digest)); + + // 4. Client receives 'ok' + pushResponse(BSON("ok" << 1)); + + // Call clientAuthenticate() + auto params = BSON("mechanism" + << "MONGODB-CR" + << "db" + << "admin" + << "user" << _username << "pwd" << _password << "digest" + << "true"); + auth::authenticateClient(params, "", "", _runCommandCallback); +} + +TEST_F(AuthClientTest, X509) { +#ifdef MONGO_CONFIG_SSL + // 1. Client sends 'authenticate' command + pushRequest("$external", + BSON("authenticate" << 1 << "mechanism" + << "MONGODB-X509" + << "user" << _username)); + + // 2. Client receives 'ok' + pushResponse(BSON("ok" << 1)); + + // Call clientAuthenticate() + auto params = BSON("mechanism" + << "MONGODB-X509" + << "db" + << "$external" + << "user" << _username); + auth::authenticateClient(params, "", _username, _runCommandCallback); +#endif +} + +} // namespace diff --git a/src/mongo/client/dbclient.cpp b/src/mongo/client/dbclient.cpp index edbf462f9b3..f3d7fe3cc30 100644 --- a/src/mongo/client/dbclient.cpp +++ b/src/mongo/client/dbclient.cpp @@ -34,18 +34,18 @@ #include <utility> #include "mongo/base/status.h" -#include "mongo/bson/util/bson_extract.h" +#include "mongo/base/status_with.h" #include "mongo/bson/util/builder.h" +#include "mongo/client/authenticate.h" #include "mongo/client/constants.h" #include "mongo/client/dbclientcursor.h" #include "mongo/client/dbclientinterface.h" #include "mongo/client/replica_set_monitor.h" -#include "mongo/client/sasl_client_authenticate.h" #include "mongo/config.h" -#include "mongo/db/auth/internal_user_auth.h" #include "mongo/db/json.h" #include "mongo/db/namespace_string.h" #include "mongo/db/wire_version.h" +#include "mongo/executor/remote_command_request.h" #include "mongo/executor/remote_command_response.h" #include "mongo/rpc/factory.h" #include "mongo/rpc/get_status_from_command_result.h" @@ -74,9 +74,10 @@ using std::string; using std::stringstream; using std::vector; -namespace { +using executor::RemoteCommandRequest; +using executor::RemoteCommandResponse; -const char* const saslCommandUserSourceFieldName = "userSource"; +namespace { #ifdef MONGO_CONFIG_SSL static SimpleMutex s_mtx; @@ -493,8 +494,6 @@ BSONObj DBClientWithCommands::getPrevError() { return info; } -BSONObj getnoncecmdobj = fromjson("{getnonce:1}"); - string DBClientWithCommands::createPasswordDigest(const string& username, const string& clearTextPassword) { return mongo::createPasswordDigest(username, clearTextPassword); @@ -520,83 +519,40 @@ private: } // namespace void DBClientWithCommands::_auth(const BSONObj& params) { - ScopedMetadataWriterRemover{this}; - - std::string mechanism; + ScopedMetadataWriterRemover remover{this}; - uassertStatusOK(bsonExtractStringField(params, saslCommandMechanismFieldName, &mechanism)); - - uassert(17232, - "You cannot specify both 'db' and 'userSource'. Please use only 'db'.", - !(params.hasField(saslCommandUserDBFieldName) && - params.hasField(saslCommandUserSourceFieldName))); - - if (mechanism == StringData("MONGODB-CR", StringData::LiteralTag())) { - std::string db; - if (params.hasField(saslCommandUserSourceFieldName)) { - uassertStatusOK(bsonExtractStringField(params, saslCommandUserSourceFieldName, &db)); - } else { - uassertStatusOK(bsonExtractStringField(params, saslCommandUserDBFieldName, &db)); - } - std::string user; - uassertStatusOK(bsonExtractStringField(params, saslCommandUserFieldName, &user)); - std::string password; - uassertStatusOK(bsonExtractStringField(params, saslCommandPasswordFieldName, &password)); - bool digestPassword; - uassertStatusOK(bsonExtractBooleanFieldWithDefault( - params, saslCommandDigestPasswordFieldName, true, &digestPassword)); - BSONObj result; - uassert(result["code"].Int(), - result.toString(), - _authMongoCR(db, user, password, &result, digestPassword)); - } + // We will only have a client name if SSL is enabled + std::string clientName = ""; #ifdef MONGO_CONFIG_SSL - else if (mechanism == StringData("MONGODB-X509", StringData::LiteralTag())) { - std::string db; - if (params.hasField(saslCommandUserSourceFieldName)) { - uassertStatusOK(bsonExtractStringField(params, saslCommandUserSourceFieldName, &db)); - } else { - uassertStatusOK(bsonExtractStringField(params, saslCommandUserDBFieldName, &db)); - } - std::string user; - uassertStatusOK(bsonExtractStringField(params, saslCommandUserFieldName, &user)); + if (sslManager() != nullptr) { + clientName = sslManager()->getSSLConfiguration().clientSubjectName; + } +#endif - uassert(ErrorCodes::AuthenticationFailed, - "Please enable SSL on the client-side to use the MONGODB-X509 " - "authentication mechanism.", - getSSLManager() != NULL); + auth::authenticateClient( + params, + HostAndPort(getServerAddress()).host(), + clientName, + [this](RemoteCommandRequest request, auth::RunCommandResultHandler handler) { + BSONObj info; + auto start = Date_t::now(); - uassert(ErrorCodes::AuthenticationFailed, - "Username \"" + user + "\" does not match the provided client certificate user \"" + - getSSLManager()->getSSLConfiguration().clientSubjectName + "\"", - user == getSSLManager()->getSSLConfiguration().clientSubjectName); + auto commandName = request.cmdObj.firstElementFieldName(); + auto reply = runCommandWithMetadata( + request.dbname, commandName, request.metadata, request.cmdObj); - BSONObj result; - uassert(result["code"].Int(), result.toString(), _authX509(db, user, &result)); - } -#endif - else if (saslClientAuthenticate != NULL) { - uassertStatusOK(saslClientAuthenticate(this, params)); - } else { - uasserted(ErrorCodes::BadValue, - mechanism + " mechanism support not compiled into client library."); - } -}; + BSONObj data = reply->getCommandReply().getOwned(); + BSONObj metadata = reply->getMetadata().getOwned(); + Milliseconds millis(Date_t::now() - start); -void DBClientWithCommands::auth(const BSONObj& params) { - try { - _auth(params); - return; - } catch (const UserException& ex) { - if (getFallbackAuthParams(params).isEmpty() || - (ex.getCode() != ErrorCodes::BadValue && ex.getCode() != ErrorCodes::CommandNotFound)) { - throw ex; - } - } + // Hand control back to authenticateClient() + handler( + StatusWith<RemoteCommandResponse>(RemoteCommandResponse(data, metadata, millis))); + }); +} - // BadValue or CommandNotFound indicates unsupported auth mechanism so fall back to - // MONGODB-CR for 2.6 compatibility. - _auth(getFallbackAuthParams(params)); +void DBClientWithCommands::auth(const BSONObj& params) { + _auth(params); } bool DBClientWithCommands::auth(const string& dbname, @@ -605,10 +561,9 @@ bool DBClientWithCommands::auth(const string& dbname, string& errmsg, bool digestPassword) { try { - auth(BSON(saslCommandMechanismFieldName - << "SCRAM-SHA-1" << saslCommandUserDBFieldName << dbname - << saslCommandUserFieldName << username << saslCommandPasswordFieldName - << password_text << saslCommandDigestPasswordFieldName << digestPassword)); + const auto authParams = + auth::buildAuthParams(dbname, username, password_text, digestPassword); + auth(authParams); return true; } catch (const UserException& ex) { if (ex.getCode() != ErrorCodes::AuthenticationFailed) @@ -618,64 +573,6 @@ bool DBClientWithCommands::auth(const string& dbname, } } -bool DBClientWithCommands::_authMongoCR(const string& dbname, - const string& username, - const string& password_text, - BSONObj* info, - bool digestPassword) { - string password = password_text; - if (digestPassword) - password = createPasswordDigest(username, password_text); - - string nonce; - if (!runCommand(dbname, getnoncecmdobj, *info)) { - return false; - } - { - BSONElement e = info->getField("nonce"); - verify(e.type() == String); - nonce = e.valuestr(); - } - - BSONObj authCmd; - BSONObjBuilder b; - { - b << "authenticate" << 1 << "nonce" << nonce << "user" << username; - md5digest d; - { - md5_state_t st; - md5_init(&st); - md5_append(&st, (const md5_byte_t*)nonce.c_str(), nonce.size()); - md5_append(&st, (const md5_byte_t*)username.data(), username.length()); - md5_append(&st, (const md5_byte_t*)password.c_str(), password.size()); - md5_finish(&st, d); - } - b << "key" << digestToString(d); - authCmd = b.done(); - } - - if (runCommand(dbname, authCmd, *info)) { - return true; - } - - return false; -} - -bool DBClientWithCommands::_authX509(const string& dbname, const string& username, BSONObj* info) { - BSONObj authCmd; - BSONObjBuilder cmdBuilder; - cmdBuilder << "authenticate" << 1 << "mechanism" - << "MONGODB-X509" - << "user" << username; - authCmd = cmdBuilder.done(); - - if (runCommand(dbname, authCmd, *info)) { - return true; - } - - return false; -} - void DBClientWithCommands::logout(const string& dbname, BSONObj& info) { runCommand(dbname, BSON("logout" << 1), info); } @@ -856,7 +753,7 @@ void DBClientConnection::_auth(const BSONObj& params) { /* note we remember the auth info before we attempt to auth -- if the connection is broken, * we will then have it for the next autoreconnect attempt. */ - authCache[params[saslCommandUserDBFieldName].str()] = params.getOwned(); + authCache[params[auth::getSaslCommandUserDBFieldName()].str()] = params.getOwned(); } DBClientBase::_auth(params); @@ -1095,8 +992,10 @@ void DBClientConnection::_checkConnection() { } catch (UserException& ex) { if (ex.getCode() != ErrorCodes::AuthenticationFailed) throw; - LOG(_logLevel) << "reconnect: auth failed " << i->second[saslCommandUserDBFieldName] - << i->second[saslCommandUserFieldName] << ' ' << ex.what() << std::endl; + LOG(_logLevel) << "reconnect: auth failed " + << i->second[auth::getSaslCommandUserDBFieldName()] + << i->second[auth::getSaslCommandUserFieldName()] << ' ' << ex.what() + << std::endl; } } } diff --git a/src/mongo/client/dbclientinterface.h b/src/mongo/client/dbclientinterface.h index e72fdbcefd8..e4f90c82dea 100644 --- a/src/mongo/client/dbclientinterface.h +++ b/src/mongo/client/dbclientinterface.h @@ -925,24 +925,6 @@ protected: virtual void _auth(const BSONObj& params); - /** - * Use the MONGODB-CR protocol to authenticate as "username" against the database "dbname", - * with the given password. If digestPassword is false, the password is assumed to be - * pre-digested. Returns false on failure, and sets "errmsg". - */ - bool _authMongoCR(const std::string& dbname, - const std::string& username, - const std::string& pwd, - BSONObj* info, - bool digestPassword); - - /** - * Use the MONGODB-X509 protocol to authenticate as "username. The certificate details - * has already been communicated automatically as part of the connect call. - * Returns false on failure and set "errmsg". - */ - bool _authX509(const std::string& dbname, const std::string& username, BSONObj* info); - // should be set by subclasses during connection. void _setServerRPCProtocols(rpc::ProtocolSet serverProtocols); diff --git a/src/mongo/client/sasl_client_authenticate.cpp b/src/mongo/client/sasl_client_authenticate.cpp index e7ad3e57ece..4222cc9a0f6 100644 --- a/src/mongo/client/sasl_client_authenticate.cpp +++ b/src/mongo/client/sasl_client_authenticate.cpp @@ -38,7 +38,8 @@ namespace mongo { using namespace mongoutils; -Status (*saslClientAuthenticate)(DBClientWithCommands* client, +Status (*saslClientAuthenticate)(RunCommandHook runCommand, + StringData hostname, const BSONObj& saslParameters) = NULL; const char* const saslStartCommandName = "saslStart"; diff --git a/src/mongo/client/sasl_client_authenticate.h b/src/mongo/client/sasl_client_authenticate.h index d1f2e36346d..f929c5660e2 100644 --- a/src/mongo/client/sasl_client_authenticate.h +++ b/src/mongo/client/sasl_client_authenticate.h @@ -29,16 +29,20 @@ #include "mongo/base/status.h" #include "mongo/bson/bsontypes.h" -#include "mongo/client/dbclientinterface.h" +#include "mongo/executor/remote_command_request.h" +#include "mongo/executor/remote_command_response.h" namespace mongo { class BSONObj; +using RunCommandResultHandler = stdx::function<void(StatusWith<executor::RemoteCommandResponse>)>; +using RunCommandHook = + stdx::function<void(executor::RemoteCommandRequest, RunCommandResultHandler)>; + /** * Attempts to authenticate "client" using the SASL protocol. * - * Do not use directly in client code. Use the DBClientWithCommands::auth(const BSONObj&) - * method, instead. + * Do not use directly in client code. Use the auth::authenticateClient() method, instead. * * Test against NULL for availability. Client driver must be compiled with SASL support _and_ * client application must have successfully executed mongo::runGlobalInitializersOrDie() or its @@ -66,7 +70,8 @@ class BSONObj; * rejected. Other failures, all of which are tantamount to authentication failure, may also be * returned. */ -extern Status (*saslClientAuthenticate)(DBClientWithCommands* client, +extern Status (*saslClientAuthenticate)(RunCommandHook runCommand, + StringData hostname, const BSONObj& saslParameters); /** diff --git a/src/mongo/client/sasl_client_authenticate_impl.cpp b/src/mongo/client/sasl_client_authenticate_impl.cpp index cde6787cebb..14f063e8972 100644 --- a/src/mongo/client/sasl_client_authenticate_impl.cpp +++ b/src/mongo/client/sasl_client_authenticate_impl.cpp @@ -45,15 +45,17 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/client/sasl_client_session.h" +#include "mongo/rpc/get_status_from_command_result.h" #include "mongo/util/base64.h" #include "mongo/util/log.h" -#include "mongo/util/mongoutils/str.h" #include "mongo/util/net/hostandport.h" #include "mongo/util/password_digest.h" namespace mongo { using std::endl; +using executor::RemoteCommandRequest; +using executor::RemoteCommandResponse; namespace { @@ -114,8 +116,9 @@ Status extractPassword(const BSONObj& saslParameters, * Returns Status::OK() on success. */ Status configureSession(SaslClientSession* session, - DBClientWithCommands* client, - const std::string& targetDatabase, + RunCommandHook runCommand, + StringData hostname, + StringData targetDatabase, const BSONObj& saslParameters) { std::string mechanism; Status status = @@ -131,10 +134,8 @@ Status configureSession(SaslClientSession* session, return status; session->setParameter(SaslClientSession::parameterServiceName, value); - status = bsonExtractStringFieldWithDefault(saslParameters, - saslCommandServiceHostnameFieldName, - HostAndPort(client->getServerAddress()).host(), - &value); + status = bsonExtractStringFieldWithDefault( + saslParameters, saslCommandServiceHostnameFieldName, hostname, &value); if (!status.isOK()) return status; session->setParameter(SaslClientSession::parameterServiceHostname, value); @@ -167,7 +168,9 @@ Status configureSession(SaslClientSession* session, * Driver for the client side of a sasl authentication session, conducted synchronously over * "client". */ -Status saslClientAuthenticateImpl(DBClientWithCommands* client, const BSONObj& saslParameters) { +Status saslClientAuthenticateImpl(RunCommandHook runCommand, + StringData hostname, + const BSONObj& saslParameters) { int saslLogLevel = getSaslClientLogLevel(saslParameters); std::string targetDatabase; @@ -188,7 +191,7 @@ Status saslClientAuthenticateImpl(DBClientWithCommands* client, const BSONObj& s } std::unique_ptr<SaslClientSession> session(SaslClientSession::create(mechanism)); - status = configureSession(session.get(), client, targetDatabase, saslParameters); + status = configureSession(session.get(), runCommand, hostname, targetDatabase, saslParameters); if (!status.isOK()) return status; @@ -232,7 +235,16 @@ Status saslClientAuthenticateImpl(DBClientWithCommands* client, const BSONObj& s // indicating a failure. Subsequent versions should return "ok: 0" on failure with a // non-zero "code" field to indicate specific failure. In all versions, ok: 1, code: >0 // and ok: 0, code optional, indicate failure. - bool ok = client->runCommand(targetDatabase, commandBuilder.obj(), inputObj); + auto request = RemoteCommandRequest(); + request.dbname = targetDatabase; + request.cmdObj = commandBuilder.obj(); + + runCommand(request, + [&inputObj](StatusWith<RemoteCommandResponse> response) { + inputObj = response.getValue().data.getOwned(); + }); + bool ok = getStatusFromCommandResult(inputObj).isOK(); + ErrorCodes::Error code = ErrorCodes::fromInt(inputObj[saslCommandCodeFieldName].numberInt()); diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 7e89f8fff74..35e0228e737 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -46,6 +46,7 @@ env.Library('authcommon', '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/bson/mutable/mutable_bson', '$BUILD_DIR/mongo/bson/util/bson_extract', + '$BUILD_DIR/mongo/client/clientdriver', '$BUILD_DIR/mongo/db/server_options_core', ]) diff --git a/src/mongo/db/auth/internal_user_auth.cpp b/src/mongo/db/auth/internal_user_auth.cpp index 5cc90c29ea9..8d00592734d 100644 --- a/src/mongo/db/auth/internal_user_auth.cpp +++ b/src/mongo/db/auth/internal_user_auth.cpp @@ -87,13 +87,6 @@ BSONObj getInternalUserAuthParamsWithFallback() { return authParams.copy(); } -BSONObj getFallbackAuthParams(BSONObj params) { - if (params["fallbackParams"].type() != Object) { - return BSONObj(); - } - return params["fallbackParams"].Obj(); -} - bool authenticateInternalUser(DBClientWithCommands* conn) { if (!isInternalAuthSet()) { if (!serverGlobalParams.quiet) { diff --git a/src/mongo/db/auth/internal_user_auth.h b/src/mongo/db/auth/internal_user_auth.h index 772eef1b322..cff260f1bf4 100644 --- a/src/mongo/db/auth/internal_user_auth.h +++ b/src/mongo/db/auth/internal_user_auth.h @@ -55,11 +55,6 @@ void setInternalUserAuthParams(const BSONObj& authParamsIn); BSONObj getInternalUserAuthParamsWithFallback(); /** - * Returns a copy of the fallback parameter portion of an internal auth parameter object - **/ -BSONObj getFallbackAuthParams(BSONObj params); - -/** * Authenticates to another cluster member using appropriate authentication data. * Uses getInternalUserAuthParams() to retrive authentication parameters. * @return true if the authentication was succesful diff --git a/src/mongo/db/commands/parameters.cpp b/src/mongo/db/commands/parameters.cpp index 7ff531bb8a1..9303246f769 100644 --- a/src/mongo/db/commands/parameters.cpp +++ b/src/mongo/db/commands/parameters.cpp @@ -32,6 +32,7 @@ #include <set> +#include "mongo/bson/json.h" #include "mongo/bson/mutable/document.h" #include "mongo/client/replica_set_monitor.h" #include "mongo/client/sasl_client_authenticate.h" diff --git a/src/mongo/db/initialize_server_global_state.cpp b/src/mongo/db/initialize_server_global_state.cpp index 62b35767235..7b7d4392267 100644 --- a/src/mongo/db/initialize_server_global_state.cpp +++ b/src/mongo/db/initialize_server_global_state.cpp @@ -62,6 +62,7 @@ #include "mongo/logger/syslog_appender.h" #include "mongo/platform/process_id.h" #include "mongo/util/log.h" +#include "mongo/util/mongoutils/str.h" #include "mongo/util/net/listen.h" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/processinfo.h" diff --git a/src/mongo/executor/SConscript b/src/mongo/executor/SConscript index 5220ea0134d..b51d3c68216 100644 --- a/src/mongo/executor/SConscript +++ b/src/mongo/executor/SConscript @@ -4,18 +4,25 @@ Import("env") env.InjectThirdPartyIncludePaths('asio') -env.Library(target='task_executor_interface', +env.Library(target='remote_command', source=[ 'remote_command_request.cpp', 'remote_command_response.cpp', - 'task_executor.cpp', ], LIBDEPS=[ - '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/rpc/metadata', '$BUILD_DIR/mongo/util/net/hostandport', ]) +env.Library(target='task_executor_interface', + source=[ + 'task_executor.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + 'remote_command', + ]) + env.Library(target='network_interface', source=['network_interface.cpp',], LIBDEPS=[ |