summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsamantharitter <samantha.ritter@10gen.com>2015-07-28 14:28:42 -0400
committersamantharitter <samantha.ritter@10gen.com>2015-08-03 10:43:33 -0400
commit328f451fa7325ae1cf01c2832a371a75e2d66843 (patch)
tree975bac47bccacb14b13204316d46a6eaa68562b4
parent97d4b72c6fc68419370620325813769003b5a70a (diff)
downloadmongo-328f451fa7325ae1cf01c2832a371a75e2d66843.tar.gz
SERVER-19455 move authentication methods to a new library
-rw-r--r--src/mongo/client/SConscript103
-rw-r--r--src/mongo/client/authenticate.cpp248
-rw-r--r--src/mongo/client/authenticate.h108
-rw-r--r--src/mongo/client/authenticate_test.cpp177
-rw-r--r--src/mongo/client/dbclient.cpp183
-rw-r--r--src/mongo/client/dbclientinterface.h18
-rw-r--r--src/mongo/client/sasl_client_authenticate.cpp3
-rw-r--r--src/mongo/client/sasl_client_authenticate.h13
-rw-r--r--src/mongo/client/sasl_client_authenticate_impl.cpp32
-rw-r--r--src/mongo/db/auth/SConscript1
-rw-r--r--src/mongo/db/auth/internal_user_auth.cpp7
-rw-r--r--src/mongo/db/auth/internal_user_auth.h5
-rw-r--r--src/mongo/db/commands/parameters.cpp1
-rw-r--r--src/mongo/db/initialize_server_global_state.cpp1
-rw-r--r--src/mongo/executor/SConscript13
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=[