diff options
25 files changed, 1330 insertions, 1155 deletions
diff --git a/jstests/auth/sasl_mechanism_discovery.js b/jstests/auth/sasl_mechanism_discovery.js index 65f930dbce9..58ccb011090 100644 --- a/jstests/auth/sasl_mechanism_discovery.js +++ b/jstests/auth/sasl_mechanism_discovery.js @@ -12,7 +12,7 @@ function checkMechs(userid, mechs) { const res = assert.commandWorked(db.runCommand({isMaster: 1, saslSupportedMechs: userid})); - assert.eq(mechs, res.saslSupportedMechs, tojson(res)); + assert.eq(mechs.sort(), res.saslSupportedMechs.sort(), tojson(res)); } // Make users. @@ -22,10 +22,14 @@ {createUser: "IX", pwd: "pwd", roles: [], mechanisms: ["SCRAM-SHA-256"]})); // Internal users should support scram methods. - checkMechs("admin.user", ["SCRAM-SHA-1", "SCRAM-SHA-256"]); + checkMechs("admin.user", ["SCRAM-SHA-256", "SCRAM-SHA-1"]); - // External users should support PLAIN, but not scram methods. - checkMechs("$external.user", ["PLAIN"]); + // External users on enterprise should support PLAIN, but not scram methods. + if (assert.commandWorked(db.runCommand({buildInfo: 1})).modules.includes("enterprise")) { + checkMechs("$external.user", ["PLAIN"]); + } else { + checkMechs("$external.user", []); + } // Check non-normalized name finds normalized user. const IXchar = "\u2168"; diff --git a/jstests/auth/system_user_exception.js b/jstests/auth/system_user_exception.js new file mode 100644 index 00000000000..5955d629135 --- /dev/null +++ b/jstests/auth/system_user_exception.js @@ -0,0 +1,21 @@ +// Test the special handling of the __system user +// works when the SCRAM-SHA-1 pw auth mechanisms are disabled. +(function() { + "use strict"; + + // Start mongod with no authentication mechanisms enabled + var m = MongoRunner.runMongod( + {keyFile: "jstests/libs/key1", setParameter: "authenticationMechanisms=PLAIN"}); + + // Verify that it's possible to use SCRAM-SHA-1 to authenticate as the __system@local user + assert.eq( + 1, m.getDB("local").auth({user: "__system", pwd: "foopdedoop", mechanism: "SCRAM-SHA-1"})); + + // Verify that it is not possible to authenticate other users + m.getDB("test").runCommand( + {createUser: "guest", pwd: "guest", roles: jsTest.readOnlyUserRoles}); + assert.eq(0, m.getDB("test").auth({user: "guest", pwd: "guest", mechanism: "SCRAM-SHA-1"})); + + MongoRunner.stopMongod(m); + +})(); diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 6ea06b5dddd..a13216efbb2 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -60,7 +60,6 @@ env.Library( 'role_graph.cpp', 'role_graph_update.cpp', 'role_graph_builtin_roles.cpp', - 'sasl_mechanism_advertiser.cpp', 'user.cpp', 'user_document_parser.cpp', 'user_management_commands_parser.cpp', @@ -147,7 +146,6 @@ env.Library('authservercommon', '$BUILD_DIR/mongo/db/commands/core', 'authcommon', 'authcore', - 'authmocks', 'authorization_manager_global', 'saslauth', 'security_file', @@ -170,14 +168,13 @@ env.Library('sasl_options', ) env.Library('saslauth', - ['native_sasl_authentication_session.cpp', - 'sasl_authentication_session.cpp', - 'sasl_plain_server_conversation.cpp', - 'sasl_scram_server_conversation.cpp', - 'sasl_server_conversation.cpp'], + [ + 'sasl_mechanism_registry.cpp', + 'sasl_plain_server_conversation.cpp', + 'sasl_scram_server_conversation.cpp', + ], LIBDEPS=[ 'authcore', - 'authmocks', # Wat? 'sasl_options', '$BUILD_DIR/mongo/base/secure_allocator', '$BUILD_DIR/mongo/db/commands/test_commands_enabled', @@ -186,6 +183,16 @@ env.Library('saslauth', ], ) +env.CppUnitTest(target='sasl_mechanism_registry_test', + source=[ + 'sasl_mechanism_registry_test.cpp', + ], + LIBDEPS=[ + 'authmocks', + 'saslauth', + '$BUILD_DIR/mongo/db/service_context_noop_init', + ]) + env.Library('authmongod', ['authz_manager_external_state_d.cpp', 'authz_session_external_state_d.cpp', @@ -226,22 +233,22 @@ env.Library( ) env.CppUnitTest('action_set_test', 'action_set_test.cpp', - LIBDEPS=['authcore', 'authmocks', 'saslauth']) + LIBDEPS=['authcore', 'authmocks']) env.CppUnitTest('privilege_parser_test', 'privilege_parser_test.cpp', - LIBDEPS=['authcore', 'authmocks', 'saslauth']) + LIBDEPS=['authcore', 'authmocks']) env.CppUnitTest('role_graph_test', 'role_graph_test.cpp', - LIBDEPS=['authcore', 'authmocks', 'saslauth']) + LIBDEPS=['authcore', 'authmocks']) env.CppUnitTest('user_document_parser_test', 'user_document_parser_test.cpp', - LIBDEPS=['authcore', 'authmocks', 'saslauth']) + LIBDEPS=['authcore', 'authmocks']) env.CppUnitTest('user_set_test', 'user_set_test.cpp', - LIBDEPS=['authcore', 'authmocks', 'saslauth']) + LIBDEPS=['authcore', 'authmocks']) env.CppUnitTest('authorization_manager_test', 'authorization_manager_test.cpp', LIBDEPS=[ '$BUILD_DIR/mongo/transport/transport_layer_common', '$BUILD_DIR/mongo/transport/transport_layer_mock', 'authcore', - 'authmocks', - 'saslauth']) + 'authmocks' + ]) env.Library( target='authorization_session_for_test', @@ -300,8 +307,10 @@ env.CppUnitTest( 'sasl_scram_test.cpp', ], LIBDEPS_PRIVATE=[ + 'authmocks', 'saslauth', '$BUILD_DIR/mongo/client/sasl_client', + '$BUILD_DIR/mongo/db/service_context_noop_init', ], ) diff --git a/src/mongo/db/auth/authentication_session.h b/src/mongo/db/auth/authentication_session.h index ed7404dc58d..c4d0f464659 100644 --- a/src/mongo/db/auth/authentication_session.h +++ b/src/mongo/db/auth/authentication_session.h @@ -30,23 +30,21 @@ #include <memory> #include "mongo/base/disallow_copying.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" namespace mongo { class Client; /** - * Abstract type representing an ongoing authentication session. - * - * An example subclass is MongoAuthenticationSession. + * Type representing an ongoing authentication session. */ class AuthenticationSession { MONGO_DISALLOW_COPYING(AuthenticationSession); public: - enum SessionType { - SESSION_TYPE_SASL // SASL authentication mechanism. - }; + explicit AuthenticationSession(std::unique_ptr<ServerMechanismBase> mech) + : _mech(std::move(mech)) {} /** * Sets the authentication session for the given "client" to "newSession". @@ -58,21 +56,17 @@ public: */ static void swap(Client* client, std::unique_ptr<AuthenticationSession>& other); - virtual ~AuthenticationSession() = default; - /** * Return an identifer of the type of session, so that a caller can safely cast it and * extract the type-specific data stored within. */ - SessionType getType() const { - return _sessionType; + ServerMechanismBase& getMechanism() const { + invariant(_mech); + return *_mech; } -protected: - explicit AuthenticationSession(SessionType sessionType) : _sessionType(sessionType) {} - private: - const SessionType _sessionType; + std::unique_ptr<ServerMechanismBase> _mech; }; } // namespace mongo diff --git a/src/mongo/db/auth/native_sasl_authentication_session.cpp b/src/mongo/db/auth/native_sasl_authentication_session.cpp deleted file mode 100644 index 74b7688f265..00000000000 --- a/src/mongo/db/auth/native_sasl_authentication_session.cpp +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2014 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/db/auth/native_sasl_authentication_session.h" - -#include <boost/range/size.hpp> - -#include "mongo/base/init.h" -#include "mongo/base/status.h" -#include "mongo/base/string_data.h" -#include "mongo/bson/util/bson_extract.h" -#include "mongo/client/sasl_client_authenticate.h" -#include "mongo/db/auth/authorization_manager.h" -#include "mongo/db/auth/authorization_manager_global.h" -#include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/authz_manager_external_state_mock.h" -#include "mongo/db/auth/authz_session_external_state_mock.h" -#include "mongo/db/auth/sasl_options.h" -#include "mongo/db/auth/sasl_plain_server_conversation.h" -#include "mongo/db/auth/sasl_scram_server_conversation.h" -#include "mongo/db/commands.h" -#include "mongo/stdx/memory.h" -#include "mongo/util/assert_util.h" -#include "mongo/util/mongoutils/str.h" -#include "mongo/util/net/sock.h" - -namespace mongo { - -using std::unique_ptr; - -namespace { -SaslAuthenticationSession* createNativeSaslAuthenticationSession(AuthorizationSession* authzSession, - StringData db, - StringData mechanism) { - return new NativeSaslAuthenticationSession(authzSession); -} - -MONGO_INITIALIZER(NativeSaslServerCore)(InitializerContext* context) { - if (saslGlobalParams.hostName.empty()) - saslGlobalParams.hostName = getHostNameCached(); - if (saslGlobalParams.serviceName.empty()) - saslGlobalParams.serviceName = "mongodb"; - - SaslAuthenticationSession::create = createNativeSaslAuthenticationSession; - return Status::OK(); -} - -// PostSaslCommands is reversely dependent on CyrusSaslCommands having been run -MONGO_INITIALIZER_WITH_PREREQUISITES(PostSaslCommands, ("NativeSaslServerCore")) -(InitializerContext*) { - AuthorizationManager authzManager(stdx::make_unique<AuthzManagerExternalStateMock>()); - std::unique_ptr<AuthorizationSession> authzSession = authzManager.makeAuthorizationSession(); - - for (size_t i = 0; i < saslGlobalParams.authenticationMechanisms.size(); ++i) { - const std::string& mechanism = saslGlobalParams.authenticationMechanisms[i]; - if (mechanism == "SCRAM-SHA-1" || mechanism == "SCRAM-SHA-256" || - mechanism == "MONGODB-X509") { - // Not a SASL mechanism; no need to smoke test built-in mechanisms. - continue; - } - unique_ptr<SaslAuthenticationSession> session( - SaslAuthenticationSession::create(authzSession.get(), "$external", mechanism)); - Status status = session->start( - "test", mechanism, saslGlobalParams.serviceName, saslGlobalParams.hostName, 1, true); - if (!status.isOK()) - return status; - } - - return Status::OK(); -} -} // namespace - -NativeSaslAuthenticationSession::NativeSaslAuthenticationSession(AuthorizationSession* authzSession) - : SaslAuthenticationSession(authzSession), _mechanism("") {} - -NativeSaslAuthenticationSession::~NativeSaslAuthenticationSession() {} - -Status NativeSaslAuthenticationSession::start(StringData authenticationDatabase, - StringData mechanism, - StringData serviceName, - StringData serviceHostname, - int64_t conversationId, - bool autoAuthorize) { - fassert(18626, conversationId > 0); - - if (_conversationId != 0) { - return Status(ErrorCodes::AlreadyInitialized, - "Cannot call start() twice on same NativeSaslAuthenticationSession."); - } - - _authenticationDatabase = authenticationDatabase.toString(); - _mechanism = mechanism.toString(); - _serviceName = serviceName.toString(); - _serviceHostname = serviceHostname.toString(); - _conversationId = conversationId; - _autoAuthorize = autoAuthorize; - - if (mechanism == "PLAIN") { - _saslConversation.reset(new SaslPLAINServerConversation(this)); - } else if (mechanism == "SCRAM-SHA-1") { - _saslConversation.reset(new SaslSCRAMServerConversationImpl<SHA1Block>(this)); - } else if (mechanism == "SCRAM-SHA-256") { - _saslConversation.reset(new SaslSCRAMServerConversationImpl<SHA256Block>(this)); - } else { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << "SASL mechanism " << mechanism - << " is not supported"); - } - - return Status::OK(); -} - -Status NativeSaslAuthenticationSession::step(StringData inputData, std::string* outputData) { - if (!_saslConversation) { - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() - << "The authentication session has not been properly initialized"); - } - - StatusWith<bool> status = _saslConversation->step(inputData, outputData); - if (status.isOK()) { - _done = status.getValue(); - } else { - _done = true; - } - return status.getStatus(); -} - -std::string NativeSaslAuthenticationSession::getPrincipalId() const { - return _saslConversation->getPrincipalId(); -} - -const char* NativeSaslAuthenticationSession::getMechanism() const { - return _mechanism.c_str(); -} - -} // namespace mongo diff --git a/src/mongo/db/auth/sasl_authentication_session.cpp b/src/mongo/db/auth/sasl_authentication_session.cpp deleted file mode 100644 index 5195a6aa193..00000000000 --- a/src/mongo/db/auth/sasl_authentication_session.cpp +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2012 10gen, 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/db/auth/sasl_authentication_session.h" - -#include <boost/range/size.hpp> - -#include "mongo/base/init.h" -#include "mongo/base/string_data.h" -#include "mongo/bson/util/bson_extract.h" -#include "mongo/client/sasl_client_authenticate.h" -#include "mongo/db/auth/authorization_manager.h" -#include "mongo/db/auth/authorization_manager_global.h" -#include "mongo/db/auth/authorization_session.h" -#include "mongo/db/auth/authz_manager_external_state_mock.h" -#include "mongo/db/auth/authz_session_external_state_mock.h" -#include "mongo/db/commands.h" -#include "mongo/util/assert_util.h" -#include "mongo/util/mongoutils/str.h" - -namespace mongo { -SaslAuthenticationSession::SaslAuthenticationSessionFactoryFn SaslAuthenticationSession::create; - -// Mechanism name constants. -const char SaslAuthenticationSession::mechanismSCRAMSHA256[] = "SCRAM-SHA-256"; -const char SaslAuthenticationSession::mechanismSCRAMSHA1[] = "SCRAM-SHA-1"; -const char SaslAuthenticationSession::mechanismGSSAPI[] = "GSSAPI"; -const char SaslAuthenticationSession::mechanismPLAIN[] = "PLAIN"; - -/** - * Standard method in mongodb for determining if "authenticatedUser" may act as "requestedUser." - * - * The standard rule in MongoDB is simple. The authenticated user name must be the same as the - * requested user name. - */ -bool isAuthorizedCommon(SaslAuthenticationSession* session, - StringData requestedUser, - StringData authenticatedUser) { - return requestedUser == authenticatedUser; -} - -SaslAuthenticationSession::SaslAuthenticationSession(AuthorizationSession* authzSession) - : AuthenticationSession(AuthenticationSession::SESSION_TYPE_SASL), - _opCtx(nullptr), - _authzSession(authzSession), - _saslStep(0), - _conversationId(0), - _autoAuthorize(false), - _done(false) {} - -SaslAuthenticationSession::~SaslAuthenticationSession(){}; - -StringData SaslAuthenticationSession::getAuthenticationDatabase() const { - if (Command::testCommandsEnabled && _authenticationDatabase == "admin" && - getPrincipalId() == internalSecurity.user->getName().getUser()) { - // Allows authenticating as the internal user against the admin database. This is to - // support the auth passthrough test framework on mongos (since you can't use the local - // database on a mongos, so you can't auth as the internal user without this). - return internalSecurity.user->getName().getDB(); - } else { - return _authenticationDatabase; - } -} - -} // namespace mongo diff --git a/src/mongo/db/auth/sasl_authentication_session.h b/src/mongo/db/auth/sasl_authentication_session.h deleted file mode 100644 index c28e60919e8..00000000000 --- a/src/mongo/db/auth/sasl_authentication_session.h +++ /dev/null @@ -1,180 +0,0 @@ -/* - * Copyright (C) 2012 10gen, 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 <cstdint> -#include <string> -#include <vector> - -#include "mongo/base/disallow_copying.h" -#include "mongo/base/status.h" -#include "mongo/base/string_data.h" -#include "mongo/db/auth/authentication_session.h" -#include "mongo/db/auth/authorization_session.h" -#include "mongo/stdx/functional.h" - -namespace mongo { - -class AuthorizationSession; -class OperationContext; - -/** - * Authentication session data for the server side of SASL authentication. - */ -class SaslAuthenticationSession : public AuthenticationSession { - MONGO_DISALLOW_COPYING(SaslAuthenticationSession); - -public: - typedef stdx::function<SaslAuthenticationSession*( - AuthorizationSession*, StringData, StringData)> - SaslAuthenticationSessionFactoryFn; - static SaslAuthenticationSessionFactoryFn create; - - // Mechanism name constants. - static const char mechanismSCRAMSHA256[]; - static const char mechanismSCRAMSHA1[]; - static const char mechanismGSSAPI[]; - static const char mechanismPLAIN[]; - - explicit SaslAuthenticationSession(AuthorizationSession* authSession); - virtual ~SaslAuthenticationSession(); - - /** - * Start the server side of a SASL authentication. - * - * "authenticationDatabase" is the database against which the user is authenticating. - * "mechanism" is the SASL mechanism to use. - * "serviceName" is the SASL service name to use. - * "serviceHostname" is the FQDN of this server. - * "conversationId" is the conversation identifier to use for this session. - * - * If "autoAuthorize" is set to true, the server will automatically acquire all privileges - * for a successfully authenticated user. If it is false, the client will need to - * explicilty acquire privileges on resources it wishes to access. - * - * Must be called only once on an instance. - */ - virtual Status start(StringData authenticationDatabase, - StringData mechanism, - StringData serviceName, - StringData serviceHostname, - int64_t conversationId, - bool autoAuthorize) = 0; - - /** - * Perform one step of the server side of the authentication session, - * consuming "inputData" and producing "*outputData". - * - * A return of Status::OK() indiciates succesful progress towards authentication. - * Any other return code indicates that authentication has failed. - * - * Must not be called before start(). - */ - virtual Status step(StringData inputData, std::string* outputData) = 0; - - /** - * Returns the the operation context associated with the currently executing command. - * Authentication commands must set this on their associated - * SaslAuthenticationSession. - */ - OperationContext* getOpCtxt() const { - return _opCtx; - } - void setOpCtxt(OperationContext* opCtx) { - _opCtx = opCtx; - } - - /** - * Gets the name of the database against which this authentication conversation is running. - * - * Not meaningful before a successful call to start(). - */ - StringData getAuthenticationDatabase() const; - - /** - * Get the conversation id for this authentication session. - * - * Must not be called before start(). - */ - int64_t getConversationId() const { - return _conversationId; - } - - /** - * If the last call to step() returned Status::OK(), this method returns true if the - * authentication conversation has completed, from the server's perspective. If it returns - * false, the server expects more input from the client. If the last call to step() did not - * return Status::OK(), returns true. - * - * Behavior is undefined if step() has not been called. - */ - bool isDone() const { - return _done; - } - - /** - * Gets the string identifier of the principal being authenticated. - * - * Returns the empty string if the session does not yet know the identity being - * authenticated. - */ - virtual std::string getPrincipalId() const = 0; - - /** - * Gets the name of the SASL mechanism in use. - * - * Returns "" if start() has not been called or if start() did not return Status::OK(). - */ - virtual const char* getMechanism() const = 0; - - /** - * Returns true if automatic privilege acquisition should be used for this principal, after - * authentication. Not meaningful before a successful call to start(). - */ - bool shouldAutoAuthorize() const { - return _autoAuthorize; - } - - AuthorizationSession* getAuthorizationSession() { - return _authzSession; - } - -protected: - OperationContext* _opCtx; - AuthorizationSession* _authzSession; - std::string _authenticationDatabase; - std::string _serviceName; - std::string _serviceHostname; - int _saslStep; - int64_t _conversationId; - bool _autoAuthorize; - bool _done; -}; - -} // namespace mongo diff --git a/src/mongo/db/auth/sasl_authentication_session_test.cpp b/src/mongo/db/auth/sasl_authentication_session_test.cpp index 7226395ece6..4babe8c816b 100644 --- a/src/mongo/db/auth/sasl_authentication_session_test.cpp +++ b/src/mongo/db/auth/sasl_authentication_session_test.cpp @@ -16,10 +16,13 @@ #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/authz_manager_external_state_mock.h" #include "mongo/db/auth/authz_session_external_state_mock.h" -#include "mongo/db/auth/sasl_authentication_session.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" #include "mongo/db/auth/sasl_options.h" +#include "mongo/db/auth/sasl_plain_server_conversation.h" +#include "mongo/db/auth/sasl_scram_server_conversation.h" #include "mongo/db/jsobj.h" #include "mongo/db/operation_context_noop.h" +#include "mongo/db/service_context_noop.h" #include "mongo/unittest/unittest.h" #include "mongo/util/log.h" #include "mongo/util/password_digest.h" @@ -38,12 +41,16 @@ public: void testWrongClientMechanism(); void testWrongServerMechanism(); + ServiceContextNoop serviceContext; + ServiceContext::UniqueClient opClient; + ServiceContext::UniqueOperationContext opCtx; AuthzManagerExternalStateMock* authManagerExternalState; - AuthorizationManager authManager; + AuthorizationManager* authManager; std::unique_ptr<AuthorizationSession> authSession; + SASLServerMechanismRegistry registry; std::string mechanism; std::unique_ptr<SaslClientSession> client; - std::unique_ptr<SaslAuthenticationSession> server; + std::unique_ptr<ServerMechanismBase> server; private: void assertConversationFailure(); @@ -58,17 +65,27 @@ const std::string mockServiceName = "mocksvc"; const std::string mockHostName = "host.mockery.com"; SaslConversation::SaslConversation(std::string mech) - : authManagerExternalState(new AuthzManagerExternalStateMock), - authManager(std::unique_ptr<AuthzManagerExternalState>(authManagerExternalState)), - authSession(authManager.makeAuthorizationSession()), + : opClient(serviceContext.makeClient("saslTest")), + opCtx(serviceContext.makeOperationContext(opClient.get())), + authManagerExternalState(new AuthzManagerExternalStateMock), + authManager(new AuthorizationManager( + std::unique_ptr<AuthzManagerExternalState>(authManagerExternalState))), + authSession(authManager->makeAuthorizationSession()), mechanism(mech) { - OperationContextNoop opCtx; + + AuthorizationManager::set(&serviceContext, std::unique_ptr<AuthorizationManager>(authManager)); client.reset(SaslClientSession::create(mechanism)); - server.reset(SaslAuthenticationSession::create(authSession.get(), "test", mechanism)); + + registry.registerFactory<PLAINServerFactory>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + registry.registerFactory<SCRAMSHA1ServerFactory>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + registry.registerFactory<SCRAMSHA256ServerFactory>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); ASSERT_OK(authManagerExternalState->updateOne( - &opCtx, + opCtx.get(), AuthorizationManager::versionCollectionNamespace, AuthorizationManager::versionDocumentQuery, BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName @@ -88,7 +105,7 @@ SaslConversation::SaslConversation(std::string mech) << scram::Secrets<SHA256Block>::generateCredentials( "frim", saslGlobalParams.scramSHA256IterationCount.load())); - ASSERT_OK(authManagerExternalState->insert(&opCtx, + ASSERT_OK(authManagerExternalState->insert(opCtx.get(), NamespaceString("admin.system.users"), BSON("_id" << "test.andy" @@ -107,16 +124,16 @@ void SaslConversation::assertConversationFailure() { std::string clientMessage; std::string serverMessage; Status clientStatus(ErrorCodes::InternalError, ""); - Status serverStatus(ErrorCodes::InternalError, ""); + StatusWith<std::string> serverResponse(""); do { - clientStatus = client->step(serverMessage, &clientMessage); + clientStatus = client->step(serverResponse.getValue(), &clientMessage); if (!clientStatus.isOK()) break; - serverStatus = server->step(clientMessage, &serverMessage); - if (!serverStatus.isOK()) + serverResponse = server->step(opCtx.get(), clientMessage); + if (!serverResponse.isOK()) break; } while (!client->isDone()); - ASSERT_FALSE(serverStatus.isOK() && clientStatus.isOK() && client->isDone() && + ASSERT_FALSE(serverResponse.isOK() && clientStatus.isOK() && client->isDone() && server->isDone()); } @@ -128,13 +145,12 @@ void SaslConversation::testSuccessfulAuthentication() { client->setParameter(SaslClientSession::parameterPassword, "frim"); ASSERT_OK(client->initialize()); - ASSERT_OK(server->start("test", mechanism, mockServiceName, mockHostName, 1, true)); - std::string clientMessage; - std::string serverMessage; + StatusWith<std::string> serverResponse(""); do { - ASSERT_OK(client->step(serverMessage, &clientMessage)); - ASSERT_OK(server->step(clientMessage, &serverMessage)); + ASSERT_OK(client->step(serverResponse.getValue(), &clientMessage)); + serverResponse = server->step(opCtx.get(), clientMessage); + ASSERT_OK(serverResponse.getStatus()); } while (!client->isDone()); ASSERT_TRUE(server->isDone()); } @@ -147,8 +163,6 @@ void SaslConversation::testNoSuchUser() { client->setParameter(SaslClientSession::parameterPassword, "frim"); ASSERT_OK(client->initialize()); - ASSERT_OK(server->start("test", mechanism, mockServiceName, mockHostName, 1, true)); - assertConversationFailure(); } @@ -160,8 +174,6 @@ void SaslConversation::testBadPassword() { client->setParameter(SaslClientSession::parameterPassword, "WRONG"); ASSERT_OK(client->initialize()); - ASSERT_OK(server->start("test", mechanism, mockServiceName, mockHostName, 1, true)); - assertConversationFailure(); } @@ -175,8 +187,6 @@ void SaslConversation::testWrongClientMechanism() { client->setParameter(SaslClientSession::parameterPassword, "frim"); ASSERT_OK(client->initialize()); - ASSERT_OK(server->start("test", mechanism, mockServiceName, mockHostName, 1, true)); - assertConversationFailure(); } @@ -188,19 +198,22 @@ void SaslConversation::testWrongServerMechanism() { client->setParameter(SaslClientSession::parameterPassword, "frim"); ASSERT_OK(client->initialize()); - ASSERT_OK(server->start("test", - mechanism != "SCRAM-SHA-1" ? "SCRAM-SHA-1" : "PLAIN", - mockServiceName, - mockHostName, - 1, - true)); + auto swServer = + registry.getServerMechanism(mechanism != "SCRAM-SHA-1" ? "SCRAM-SHA-1" : "PLAIN", "test"); + ASSERT_OK(swServer.getStatus()); + server = std::move(swServer.getValue()); + assertConversationFailure(); } -#define DEFINE_MECHANISM_FIXTURE(CLASS_SUFFIX, MECH_NAME) \ - class SaslConversation##CLASS_SUFFIX : public SaslConversation { \ - public: \ - SaslConversation##CLASS_SUFFIX() : SaslConversation(MECH_NAME) {} \ +#define DEFINE_MECHANISM_FIXTURE(CLASS_SUFFIX, MECH_NAME) \ + class SaslConversation##CLASS_SUFFIX : public SaslConversation { \ + public: \ + SaslConversation##CLASS_SUFFIX() : SaslConversation(MECH_NAME) { \ + auto swServer = registry.getServerMechanism(MECH_NAME, "test"); \ + ASSERT_OK(swServer.getStatus()); \ + server = std::move(swServer.getValue()); \ + } \ } #define DEFINE_MECHANISM_TEST(FIXTURE_NAME, TEST_NAME) \ @@ -236,7 +249,9 @@ TEST_F(SaslIllegalConversation, IllegalClientMechanism) { } TEST_F(SaslIllegalConversation, IllegalServerMechanism) { - ASSERT_NOT_OK(server->start("test", "FAKE", mockServiceName, mockHostName, 1, true)); + SASLServerMechanismRegistry registry; + auto swServer = registry.getServerMechanism("FAKE", "test"); + ASSERT_NOT_OK(swServer.getStatus()); } } // namespace diff --git a/src/mongo/db/auth/sasl_commands.cpp b/src/mongo/db/auth/sasl_commands.cpp index f54fed2842a..d11ee7f1886 100644 --- a/src/mongo/db/auth/sasl_commands.cpp +++ b/src/mongo/db/auth/sasl_commands.cpp @@ -30,6 +30,7 @@ #include "mongo/platform/basic.h" +#include <memory> #include "mongo/base/init.h" #include "mongo/base/status.h" @@ -39,10 +40,10 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/db/audit.h" +#include "mongo/db/auth/authentication_session.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/authz_manager_external_state_mock.h" #include "mongo/db/auth/authz_session_external_state_mock.h" -#include "mongo/db/auth/sasl_authentication_session.h" #include "mongo/db/auth/sasl_options.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" @@ -117,12 +118,12 @@ public: CmdSaslStart cmdSaslStart; CmdSaslContinue cmdSaslContinue; -Status buildResponse(const SaslAuthenticationSession* session, +Status buildResponse(const AuthenticationSession* session, const std::string& responsePayload, BSONType responsePayloadType, BSONObjBuilder* result) { - result->appendIntOrLL(saslCommandConversationIdFieldName, session->getConversationId()); - result->appendBool(saslCommandDoneFieldName, session->isDone()); + result->appendIntOrLL(saslCommandConversationIdFieldName, 1); + result->appendBool(saslCommandDoneFieldName, session->getMechanism().isDone()); if (responsePayload.size() > size_t(std::numeric_limits<int>::max())) { return Status(ErrorCodes::InvalidLength, "Response payload too long"); @@ -159,96 +160,98 @@ Status extractMechanism(const BSONObj& cmdObj, std::string* mechanism) { return bsonExtractStringField(cmdObj, saslCommandMechanismFieldName, mechanism); } -Status doSaslStep(const Client* client, - SaslAuthenticationSession* session, +Status doSaslStep(OperationContext* opCtx, + AuthenticationSession* session, const BSONObj& cmdObj, BSONObjBuilder* result) { std::string payload; BSONType type = EOO; Status status = saslExtractPayload(cmdObj, &payload, &type); - if (!status.isOK()) + if (!status.isOK()) { return status; + } + + auto& mechanism = session->getMechanism(); - std::string responsePayload; // Passing in a payload and extracting a responsePayload - status = session->step(payload, &responsePayload); + StatusWith<std::string> swResponse = mechanism.step(opCtx, payload); - if (!status.isOK()) { - log() << session->getMechanism() << " authentication failed for " - << session->getPrincipalId() << " on " << session->getAuthenticationDatabase() - << " from client " << client->getRemote().toString() << " ; " << redact(status); + if (!swResponse.isOK()) { + log() << "SASL " << mechanism.mechanismName() << " authentication failed for " + << mechanism.getPrincipalName() << " on " << mechanism.getAuthenticationDatabase() + << " from client " << opCtx->getClient()->getRemote().toString() << " ; " + << redact(swResponse.getStatus()); sleepmillis(saslGlobalParams.authFailedDelay.load()); // All the client needs to know is that authentication has failed. return AuthorizationManager::authenticationFailedStatus; } - status = buildResponse(session, responsePayload, type, result); - if (!status.isOK()) + status = buildResponse(session, swResponse.getValue(), type, result); + if (!status.isOK()) { return status; + } - if (session->isDone()) { - UserName userName(session->getPrincipalId(), session->getAuthenticationDatabase()); + if (mechanism.isDone()) { + UserName userName(mechanism.getPrincipalName(), mechanism.getAuthenticationDatabase()); status = - session->getAuthorizationSession()->addAndAuthorizeUser(session->getOpCtxt(), userName); + AuthorizationSession::get(opCtx->getClient())->addAndAuthorizeUser(opCtx, userName); if (!status.isOK()) { return status; } if (!serverGlobalParams.quiet.load()) { - log() << "Successfully authenticated as principal " << session->getPrincipalId() - << " on " << session->getAuthenticationDatabase(); + log() << "Successfully authenticated as principal " << mechanism.getPrincipalName() + << " on " << mechanism.getAuthenticationDatabase(); } } return Status::OK(); } -Status doSaslStart(const Client* client, - SaslAuthenticationSession* session, - const std::string& db, - const BSONObj& cmdObj, - BSONObjBuilder* result) { +StatusWith<std::unique_ptr<AuthenticationSession>> doSaslStart(OperationContext* opCtx, + const std::string& db, + const BSONObj& cmdObj, + BSONObjBuilder* result) { bool autoAuthorize = false; Status status = bsonExtractBooleanFieldWithDefault( cmdObj, saslCommandAutoAuthorizeFieldName, autoAuthorizeDefault, &autoAuthorize); if (!status.isOK()) return status; - std::string mechanism; - status = extractMechanism(cmdObj, &mechanism); + std::string mechanismName; + status = extractMechanism(cmdObj, &mechanismName); if (!status.isOK()) return status; - if (!sequenceContains(saslGlobalParams.authenticationMechanisms, mechanism) && - mechanism != "SCRAM-SHA-1") { - // Always allow SCRAM-SHA-1 to pass to the first sasl step since we need to - // handle internal user authentication, SERVER-16534 - result->append(saslCommandMechanismListFieldName, - saslGlobalParams.authenticationMechanisms); - return Status(ErrorCodes::BadValue, - mongoutils::str::stream() << "Unsupported mechanism " << mechanism); + StatusWith<std::unique_ptr<ServerMechanismBase>> swMech = + SASLServerMechanismRegistry::get(opCtx->getServiceContext()) + .getServerMechanism(mechanismName, db); + + if (!swMech.isOK()) { + return swMech.getStatus(); } - status = session->start( - db, mechanism, saslGlobalParams.serviceName, saslGlobalParams.hostName, 1, autoAuthorize); - if (!status.isOK()) - return status; + auto session = std::make_unique<AuthenticationSession>(std::move(swMech.getValue())); + Status statusStep = doSaslStep(opCtx, session.get(), cmdObj, result); + if (!statusStep.isOK()) { + return statusStep; + } - return doSaslStep(client, session, cmdObj, result); + return std::move(session); } -Status doSaslContinue(const Client* client, - SaslAuthenticationSession* session, +Status doSaslContinue(OperationContext* opCtx, + AuthenticationSession* session, const BSONObj& cmdObj, BSONObjBuilder* result) { int64_t conversationId = 0; Status status = extractConversationId(cmdObj, &conversationId); if (!status.isOK()) return status; - if (conversationId != session->getConversationId()) + if (conversationId != 1) return Status(ErrorCodes::ProtocolError, "sasl: Mismatched conversation id"); - return doSaslStep(client, session, cmdObj, result); + return doSaslStep(opCtx, session, cmdObj, result); } CmdSaslStart::CmdSaslStart() : BasicCommand(saslStartCommandName) {} @@ -269,33 +272,32 @@ bool CmdSaslStart::run(OperationContext* opCtx, const std::string& db, const BSONObj& cmdObj, BSONObjBuilder& result) { - Client* client = Client::getCurrent(); + Client* client = opCtx->getClient(); AuthenticationSession::set(client, std::unique_ptr<AuthenticationSession>()); - std::string mechanism; - if (!extractMechanism(cmdObj, &mechanism).isOK()) { + std::string mechanismName; + if (!extractMechanism(cmdObj, &mechanismName).isOK()) { return false; } - SaslAuthenticationSession* session = - SaslAuthenticationSession::create(AuthorizationSession::get(client), db, mechanism); - - std::unique_ptr<AuthenticationSession> sessionGuard(session); - - session->setOpCtxt(opCtx); - - Status status = doSaslStart(client, session, db, cmdObj, &result); - CommandHelpers::appendCommandStatus(result, status); + StatusWith<std::unique_ptr<AuthenticationSession>> swSession = + doSaslStart(opCtx, db, cmdObj, &result); + CommandHelpers::appendCommandStatus(result, swSession.getStatus()); + if (!swSession.isOK()) { + return false; + } + auto session = std::move(swSession.getValue()); - if (session->isDone()) { + auto& mechanism = session->getMechanism(); + if (mechanism.isDone()) { audit::logAuthentication(client, - session->getMechanism(), - UserName(session->getPrincipalId(), db), - status.code()); + mechanismName, + UserName(mechanism.getPrincipalName(), db), + swSession.getStatus().code()); } else { - AuthenticationSession::swap(client, sessionGuard); + AuthenticationSession::swap(client, session); } - return status.isOK(); + return swSession.isOK(); } CmdSaslContinue::CmdSaslContinue() : BasicCommand(saslContinueCommandName) {} @@ -313,33 +315,32 @@ bool CmdSaslContinue::run(OperationContext* opCtx, std::unique_ptr<AuthenticationSession> sessionGuard; AuthenticationSession::swap(client, sessionGuard); - if (!sessionGuard || sessionGuard->getType() != AuthenticationSession::SESSION_TYPE_SASL) { + if (!sessionGuard) { return CommandHelpers::appendCommandStatus( result, Status(ErrorCodes::ProtocolError, "No SASL session state found")); } - SaslAuthenticationSession* session = - static_cast<SaslAuthenticationSession*>(sessionGuard.get()); + AuthenticationSession* session = static_cast<AuthenticationSession*>(sessionGuard.get()); + auto& mechanism = session->getMechanism(); // Authenticating the __system@local user to the admin database on mongos is required // by the auth passthrough test suite. - if (session->getAuthenticationDatabase() != db && !Command::testCommandsEnabled) { + if (mechanism.getAuthenticationDatabase() != db && !Command::testCommandsEnabled) { return CommandHelpers::appendCommandStatus( result, Status(ErrorCodes::ProtocolError, "Attempt to switch database target during SASL authentication.")); } - session->setOpCtxt(opCtx); - - Status status = doSaslContinue(client, session, cmdObj, &result); + Status status = doSaslContinue(opCtx, session, cmdObj, &result); CommandHelpers::appendCommandStatus(result, status); - if (session->isDone()) { - audit::logAuthentication(client, - session->getMechanism(), - UserName(session->getPrincipalId(), db), - status.code()); + if (mechanism.isDone()) { + audit::logAuthentication( + client, + mechanism.mechanismName(), + UserName(mechanism.getPrincipalName(), mechanism.getAuthenticationDatabase()), + status.code()); } else { AuthenticationSession::swap(client, sessionGuard); } @@ -348,7 +349,7 @@ bool CmdSaslContinue::run(OperationContext* opCtx, } // The CyrusSaslCommands Enterprise initializer is dependent on PreSaslCommands -MONGO_INITIALIZER_WITH_PREREQUISITES(PreSaslCommands, ("NativeSaslServerCore")) +MONGO_INITIALIZER(PreSaslCommands) (InitializerContext*) { if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-X509")) CmdAuthenticate::disableAuthMechanism("MONGODB-X509"); diff --git a/src/mongo/db/auth/sasl_mechanism_advertiser.cpp b/src/mongo/db/auth/sasl_mechanism_advertiser.cpp deleted file mode 100644 index 24ffc0eef08..00000000000 --- a/src/mongo/db/auth/sasl_mechanism_advertiser.cpp +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (C) 2018 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 "mongo/db/auth/sasl_mechanism_advertiser.h" - -#include "mongo/crypto/sha1_block.h" -#include "mongo/db/auth/authorization_manager.h" -#include "mongo/db/auth/sasl_options.h" -#include "mongo/db/auth/user.h" -#include "mongo/util/icu.h" -#include "mongo/util/scopeguard.h" - -namespace mongo { - -namespace { -void appendMechanismIfSupported(StringData mechanism, BSONArrayBuilder* builder) { - const auto& globalMechanisms = saslGlobalParams.authenticationMechanisms; - if (std::find(globalMechanisms.begin(), globalMechanisms.end(), mechanism) != - globalMechanisms.end()) { - (*builder) << mechanism; - } -} - -/** - * Fetch credentials for the named user eithing using the unnormalized form provided, - * or the form returned from saslPrep(). - * If both forms exist as different user records, produce an error. - */ -User::CredentialData getUserCredentials(OperationContext* opCtx, const std::string& username) { - AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext()); - User* rawObj = nullptr; - User* prepObj = nullptr; - auto guard = MakeGuard([authManager, &rawObj, &prepObj] { - if (prepObj) { - authManager->releaseUser(prepObj); - } - if (rawObj) { - authManager->releaseUser(rawObj); - } - }); - - const auto rawUserName = uassertStatusOK(UserName::parse(username)); - const auto rawStatus = authManager->acquireUser(opCtx, rawUserName, &rawObj); - - // Attempt to normalize the provided username (excluding DB portion). - // If saslPrep() fails, then there can't possibly be another user with - // compatibility equivalence, so fall-through. - const auto swPrepUser = saslPrep(rawUserName.getUser()); - if (swPrepUser.isOK()) { - UserName prepUserName(swPrepUser.getValue(), rawUserName.getDB()); - if (prepUserName != rawUserName) { - // User has a SASLPREPable name which differs from the raw presentation. - // Double check that we don't have a different user by that new name. - const auto prepStatus = authManager->acquireUser(opCtx, prepUserName, &prepObj); - if (prepStatus.isOK()) { - // If both statuses are OK, then we have two distinct users with "different" names. - uassert(ErrorCodes::BadValue, - "Two users exist with names exhibiting compatibility equivalence", - !rawStatus.isOK()); - // Otherwise, only the normalized version exists. - return prepObj->getCredentials(); - } - } - } - - uassertStatusOK(rawStatus); - return rawObj->getCredentials(); -} - -} // namespace - - -void SASLMechanismAdvertiser::advertise(OperationContext* opCtx, - const BSONObj& cmdObj, - BSONObjBuilder* result) { - BSONElement saslSupportedMechs = cmdObj["saslSupportedMechs"]; - if (saslSupportedMechs.type() == BSONType::String) { - const auto credentials = getUserCredentials(opCtx, saslSupportedMechs.String()); - - BSONArrayBuilder mechanismsBuilder; - if (credentials.isExternal) { - for (const StringData& userMechanism : {"GSSAPI", "PLAIN"}) { - appendMechanismIfSupported(userMechanism, &mechanismsBuilder); - } - } - if (credentials.scram<SHA1Block>().isValid()) { - appendMechanismIfSupported("SCRAM-SHA-1", &mechanismsBuilder); - } - if (credentials.scram<SHA256Block>().isValid()) { - appendMechanismIfSupported("SCRAM-SHA-256", &mechanismsBuilder); - } - - result->appendArray("saslSupportedMechs", mechanismsBuilder.arr()); - } -} - - -} // namespace mongo diff --git a/src/mongo/db/auth/sasl_mechanism_advertiser.h b/src/mongo/db/auth/sasl_mechanism_advertiser.h deleted file mode 100644 index cae0e65895d..00000000000 --- a/src/mongo/db/auth/sasl_mechanism_advertiser.h +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (C) 2018 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 "mongo/bson/bsonobj.h" -#include "mongo/bson/bsonobjbuilder.h" -#include "mongo/db/operation_context.h" - -namespace mongo { - -class SASLMechanismAdvertiser { -public: - static void advertise(OperationContext* opCtx, const BSONObj& cmdObj, BSONObjBuilder* result); -}; - -} // namespace mongo diff --git a/src/mongo/db/auth/native_sasl_authentication_session.h b/src/mongo/db/auth/sasl_mechanism_policies.h index 3688a4aad31..bdb6b98d48d 100644 --- a/src/mongo/db/auth/native_sasl_authentication_session.h +++ b/src/mongo/db/auth/sasl_mechanism_policies.h @@ -1,5 +1,5 @@ -/* - * Copyright (C) 2014 10gen, Inc. +/** + * Copyright (C) 2018 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, @@ -28,43 +28,51 @@ #pragma once -#include <cstdint> -#include <string> - -#include "mongo/base/disallow_copying.h" -#include "mongo/base/status.h" #include "mongo/base/string_data.h" -#include "mongo/db/auth/authentication_session.h" -#include "mongo/db/auth/sasl_authentication_session.h" -#include "mongo/db/auth/sasl_server_conversation.h" +#include "mongo/crypto/sha_block.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" namespace mongo { -/** - * Authentication session data for the server side of SASL authentication. - */ -class NativeSaslAuthenticationSession : public SaslAuthenticationSession { - MONGO_DISALLOW_COPYING(NativeSaslAuthenticationSession); - -public: - explicit NativeSaslAuthenticationSession(AuthorizationSession* authSession); - virtual ~NativeSaslAuthenticationSession(); +struct PLAINPolicy { + static constexpr StringData getName() { + return "PLAIN"_sd; + } + static SecurityPropertySet getProperties() { + return SecurityPropertySet{}; + } +}; - virtual Status start(StringData authenticationDatabase, - StringData mechanism, - StringData serviceName, - StringData serviceHostname, - int64_t conversationId, - bool autoAuthorize); +struct SCRAMSHA1Policy { + using HashBlock = SHA1Block; - virtual Status step(StringData inputData, std::string* outputData); + static constexpr StringData getName() { + return "SCRAM-SHA-1"_sd; + } + static SecurityPropertySet getProperties() { + return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth}; + } +}; - virtual std::string getPrincipalId() const; +struct SCRAMSHA256Policy { + using HashBlock = SHA256Block; - virtual const char* getMechanism() const; + static constexpr StringData getName() { + return "SCRAM-SHA-256"_sd; + } + static SecurityPropertySet getProperties() { + return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth}; + } +}; -private: - std::string _mechanism; - std::unique_ptr<SaslServerConversation> _saslConversation; +struct GSSAPIPolicy { + static constexpr StringData getName() { + return "GSSAPI"_sd; + } + static SecurityPropertySet getProperties() { + return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth}; + } }; + + } // namespace mongo diff --git a/src/mongo/db/auth/sasl_mechanism_registry.cpp b/src/mongo/db/auth/sasl_mechanism_registry.cpp new file mode 100644 index 00000000000..b46ba9978f3 --- /dev/null +++ b/src/mongo/db/auth/sasl_mechanism_registry.cpp @@ -0,0 +1,179 @@ +/** + * Copyright (C) 2018 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 "mongo/db/auth/sasl_mechanism_registry.h" + +#include "mongo/base/init.h" +#include "mongo/db/auth/sasl_options.h" +#include "mongo/db/auth/user.h" +#include "mongo/util/icu.h" +#include "mongo/util/net/sock.h" +#include "mongo/util/scopeguard.h" +#include "mongo/util/sequence_util.h" + +namespace mongo { + +namespace { +const auto getSASLServerMechanismRegistry = + ServiceContext::declareDecoration<std::unique_ptr<SASLServerMechanismRegistry>>(); + +/** + * Fetch credentials for the named user either using the unnormalized form provided, + * or the form returned from saslPrep(). + * If both forms exist as different user records, produce an error. + */ +User* getUserPtr(OperationContext* opCtx, const UserName& userName) { + AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext()); + + User* rawObj = nullptr; + const auto rawStatus = authManager->acquireUser(opCtx, userName, &rawObj); + auto rawGuard = MakeGuard([authManager, &rawObj] { + if (rawObj) { + authManager->releaseUser(rawObj); + } + }); + + // Attempt to normalize the provided username (excluding DB portion). + // If saslPrep() fails, then there can't possibly be another user with + // compatibility equivalence, so fall-through. + const auto swPrepUser = saslPrep(userName.getUser()); + if (swPrepUser.isOK()) { + UserName prepUserName(swPrepUser.getValue(), userName.getDB()); + if (prepUserName != userName) { + // User has a SASLPREPable name which differs from the raw presentation. + // Double check that we don't have a different user by that new name. + User* prepObj = nullptr; + const auto prepStatus = authManager->acquireUser(opCtx, prepUserName, &prepObj); + + auto prepGuard = MakeGuard([authManager, &prepObj] { + if (prepObj) { + authManager->releaseUser(prepObj); + } + }); + + if (prepStatus.isOK()) { + // If both statuses are OK, then we have two distinct users with "different" names. + uassert(ErrorCodes::BadValue, + "Two users exist with names exhibiting compatibility equivalence", + !rawStatus.isOK()); + // Otherwise, only the normalized version exists. + User* returnObj = nullptr; + std::swap(returnObj, prepObj); + return returnObj; + } + } + } + + uassertStatusOK(rawStatus); + User* returnObj = nullptr; + std::swap(returnObj, rawObj); + return returnObj; +} + +} // namespace + +SASLServerMechanismRegistry& SASLServerMechanismRegistry::get(ServiceContext* serviceContext) { + auto& uptr = getSASLServerMechanismRegistry(serviceContext); + invariant(uptr); + return *uptr; +} + +void SASLServerMechanismRegistry::set(ServiceContext* service, + std::unique_ptr<SASLServerMechanismRegistry> registry) { + getSASLServerMechanismRegistry(service) = std::move(registry); +} + +StatusWith<std::unique_ptr<ServerMechanismBase>> SASLServerMechanismRegistry::getServerMechanism( + StringData mechanismName, std::string authenticationDatabase) { + auto& map = _getMapRef(authenticationDatabase); + + auto it = map.find(mechanismName.toString()); + if (it != map.end()) { + return it->second->create(std::move(authenticationDatabase)); + } + + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << "Unsupported mechanism '" << mechanismName + << "' on authentication database '" + << authenticationDatabase + << "'"); +} + +void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContext* opCtx, + const BSONObj& isMasterCmd, + BSONObjBuilder* builder) { + BSONElement saslSupportedMechs = isMasterCmd["saslSupportedMechs"]; + if (saslSupportedMechs.type() == BSONType::String) { + + const auto userName = uassertStatusOK(UserName::parse(saslSupportedMechs.String())); + const auto userPtr = getUserPtr(opCtx, userName); + auto guard = MakeGuard([&opCtx, &userPtr] { + AuthorizationManager* authManager = + AuthorizationManager::get(opCtx->getServiceContext()); + authManager->releaseUser(userPtr); + }); + + BSONArrayBuilder mechanismsBuilder; + auto& map = _getMapRef(userName.getDB()); + + for (const auto& factoryIt : map) { + SecurityPropertySet properties = factoryIt.second->properties(); + if (!properties.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText, + SecurityProperty::kMutualAuth}) && + userName.getDB() != "$external") { + continue; + } + + if (factoryIt.second->canMakeMechanismForUser(userPtr)) { + mechanismsBuilder << factoryIt.first; + } + } + + builder->appendArray("saslSupportedMechs", mechanismsBuilder.arr()); + } +} + +bool SASLServerMechanismRegistry::_mechanismSupportedByConfig(StringData mechName) { + return sequenceContains(saslGlobalParams.authenticationMechanisms, mechName); +} + +MONGO_INITIALIZER_WITH_PREREQUISITES(CreateSASLServerMechanismRegistry, ("SetGlobalEnvironment")) +(::mongo::InitializerContext* context) { + if (saslGlobalParams.hostName.empty()) + saslGlobalParams.hostName = getHostNameCached(); + if (saslGlobalParams.serviceName.empty()) + saslGlobalParams.serviceName = "mongodb"; + + auto registry = stdx::make_unique<SASLServerMechanismRegistry>(); + SASLServerMechanismRegistry::set(getGlobalServiceContext(), std::move(registry)); + return Status::OK(); +} + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_mechanism_registry.h b/src/mongo/db/auth/sasl_mechanism_registry.h new file mode 100644 index 00000000000..6a6d8fe9d33 --- /dev/null +++ b/src/mongo/db/auth/sasl_mechanism_registry.h @@ -0,0 +1,336 @@ +/** + * Copyright (C) 2018 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 <memory> +#include <unordered_map> + +#include "mongo/base/string_data.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/commands.h" +#include "mongo/db/operation_context.h" +#include "mongo/db/service_context.h" + +namespace mongo { + +class User; + +/** + * The set of attributes SASL mechanisms may possess. + * Different SASL mechanisms provide different types of assurances to clients and servers. + * These SecurityProperties are attached to mechanism types to allow reasoning about them. + * + * Descriptions of individual properties assumed while using a mechanism with the property. + */ +enum class SecurityProperty : size_t { + kMutualAuth, // Both clients and servers are assured of the other's identity. + kNoPlainText, // Messages sent across the wire don't include the plaintext password. + kLastSecurityProperty, // Do not use. An enum value for internal bookkeeping. +}; + +/** Allows a set of SecurityProperties to be defined in an initializer list. */ +using SecurityPropertyList = std::initializer_list<SecurityProperty>; + +/** A set of SecurityProperties which may be exhibited by a SASL mechanism. */ +class SecurityPropertySet { +public: + explicit SecurityPropertySet(SecurityPropertyList props) { + for (auto prop : props) { + _set.set(static_cast<size_t>(prop)); + } + } + + /** Returns true if the set contains all of the requested properties. */ + bool hasAllProperties(SecurityPropertySet propertySet) const { + for (size_t i = 0; i < propertySet._set.size(); ++i) { + if (propertySet._set[i] && !_set[i]) { + return false; + } + } + return true; + } + + +private: + std::bitset<static_cast<size_t>(SecurityProperty::kLastSecurityProperty)> _set; +}; + +/** + * SASL server mechanisms are made by a corresponding factory. + * Mechanisms have properties. We wish to be able to manipulate these properties both at runtime + * and compile-time. These properties should apply to, and be accessible from, mechanisms and + * factories. Client mechanisms/factories should be able to use the same property definitions. + * This allows safe mechanism negotiation. + * + * The properties are set by creating a Policy class, and making mechanisms inherit from a helper + * derived from the class. Factories are derived from a helper which uses the mechanism. + */ + +/** Exposes properties of the SASL mechanism. */ +class SaslServerCommonBase { +public: + virtual ~SaslServerCommonBase() = default; + virtual StringData mechanismName() const = 0; + virtual SecurityPropertySet properties() const = 0; +}; + +/** + * Base class shared by all server-side SASL mechanisms. + */ +class ServerMechanismBase : public SaslServerCommonBase { +public: + explicit ServerMechanismBase(std::string authenticationDatabase) + : _authenticationDatabase(std::move(authenticationDatabase)) {} + + virtual ~ServerMechanismBase() = default; + + /** + * Returns the principal name which this mechanism is performing authentication for. + * This name is provided by the client, in the SASL messages they send the server. + * This value may not be available until after some number of conversation steps have + * occurred. + * + * This method is virtual so more complex implementations can obtain this value from a + * non-member. + */ + virtual StringData getPrincipalName() const { + return _principalName; + } + + /** + * Standard method in mongodb for determining if "authenticatedUser" may act as "requestedUser." + * + * The standard rule in MongoDB is simple. The authenticated user name must be the same as the + * requested user name. + */ + virtual bool isAuthorizedToActAs(StringData requestedUser, StringData authenticatedUser) { + return requestedUser == authenticatedUser; + } + + /** + * Performs a single step of a SASL exchange. Takes an input provided by a client, + * and either returns an error, or a response to be sent back. + */ + StatusWith<std::string> step(OperationContext* opCtx, StringData input) { + auto result = stepImpl(opCtx, input); + if (result.isOK()) { + bool isDone; + std::string responseMessage; + std::tie(isDone, responseMessage) = result.getValue(); + + _done = isDone; + return responseMessage; + } + return result.getStatus(); + } + + /** + * Returns true if the conversation has completed. + * Note that this does not mean authentication succeeded! + * An error may have occurred. + */ + bool isDone() const { + return _done; + } + + /** Returns which database contains the user which authentication is being performed against. */ + StringData getAuthenticationDatabase() const { + if (Command::testCommandsEnabled && _authenticationDatabase == "admin" && + getPrincipalName() == internalSecurity.user->getName().getUser()) { + // Allows authenticating as the internal user against the admin database. This is to + // support the auth passthrough test framework on mongos (since you can't use the local + // database on a mongos, so you can't auth as the internal user without this). + return internalSecurity.user->getName().getDB(); + } else { + return _authenticationDatabase; + } + } + +protected: + /** + * Mechanism provided step implementation. + * On failure, returns a non-OK status. On success, returns a tuple consisting of + * a boolean indicating whether the mechanism has completed, and the string + * containing the server's response to the client. + */ + virtual StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx, + StringData input) = 0; + + bool _done = false; + std::string _principalName; + std::string _authenticationDatabase; +}; + +/** Base class for server mechanism factories. */ +class ServerFactoryBase : public SaslServerCommonBase { +public: + /** + * Returns if the factory is capable of producing a server mechanism object which could + * authenticate the provided user. + */ + virtual bool canMakeMechanismForUser(const User* user) const = 0; + + /** Produces a unique_ptr containing a server SASL mechanism.*/ + std::unique_ptr<ServerMechanismBase> create(std::string authenticationDatabase) { + std::unique_ptr<ServerMechanismBase> rv( + this->createImpl(std::move(authenticationDatabase))); + invariant(rv->mechanismName() == this->mechanismName()); + return rv; + } + +private: + virtual ServerMechanismBase* createImpl(std::string authenticationDatabase) = 0; +}; + +/** Instantiates a class which provides runtime access to Policy properties. */ +template <typename Policy> +class MakeServerMechanism : public ServerMechanismBase { +public: + explicit MakeServerMechanism(std::string authenticationDatabase) + : ServerMechanismBase(std::move(authenticationDatabase)) {} + virtual ~MakeServerMechanism() = default; + + using policy_type = Policy; + + StringData mechanismName() const final { + return policy_type::getName(); + } + + SecurityPropertySet properties() const final { + return policy_type::getProperties(); + } +}; + +/** Instantiates a class which provides runtime access to Policy properties. */ +template <typename ServerMechanism> +class MakeServerFactory : public ServerFactoryBase { +public: + static_assert(std::is_base_of<MakeServerMechanism<typename ServerMechanism::policy_type>, + ServerMechanism>::value, + "MakeServerFactory must be instantiated with a ServerMechanism derived from " + "MakeServerMechanism"); + + using mechanism_type = ServerMechanism; + using policy_type = typename ServerMechanism::policy_type; + + virtual ServerMechanism* createImpl(std::string authenticationDatabase) override { + return new ServerMechanism(std::move(authenticationDatabase)); + } + + StringData mechanismName() const final { + return policy_type::getName(); + } + + SecurityPropertySet properties() const final { + return policy_type::getProperties(); + } +}; + +/** + * Tracks server-side SASL mechanisms. Mechanisms' factories are registered with this class during + * server initialization. During authentication, this class finds a factory, to obtains a + * mechanism from. Also capable of producing a list of mechanisms which would be valid for a + * particular user. + */ +class SASLServerMechanismRegistry { +public: + static SASLServerMechanismRegistry& get(ServiceContext* serviceContext); + static void set(ServiceContext* service, std::unique_ptr<SASLServerMechanismRegistry> registry); + + /** + * Produces a list of SASL mechanisms which can be used to authenticate as a user. + * If isMasterCmd contains a field with a username called 'saslSupportedMechs', + * will populate 'builder' with an Array called saslSupportedMechs containing each mechanism the + * user supports. + */ + void advertiseMechanismNamesForUser(OperationContext* opCtx, + const BSONObj& isMasterCmd, + BSONObjBuilder* builder); + + /** + * Gets a mechanism object which corresponds to the provided name. + * The mechanism will be able to authenticate users which exist on the + * "authenticationDatabase". + */ + StatusWith<std::unique_ptr<ServerMechanismBase>> getServerMechanism( + StringData mechanismName, std::string authenticationDatabase); + + /** + * Registers a factory T to produce a type of SASL mechanism. + * If 'internal' is false, the factory will be used to create mechanisms for authentication + * attempts on $external. Otherwise, the mechanism may be used for any database but $external. + * This allows distinct mechanisms with the same name for the servers' different authentication + * domains. + */ + enum ValidateGlobalMechanisms : bool { + kValidateGlobalMechanisms = true, + kNoValidateGlobalMechanisms = false + }; + template <typename T> + bool registerFactory( + ValidateGlobalMechanisms validateGlobalConfig = kValidateGlobalMechanisms) { + using policy_type = typename T::policy_type; + auto mechName = policy_type::getName(); + + // Always allow SCRAM-SHA-1 to pass to the first sasl step since we need to + // handle internal user authentication, SERVER-16534 + if (validateGlobalConfig && + (mechName != "SCRAM-SHA-1" && !_mechanismSupportedByConfig(mechName))) { + return false; + } + + invariant(_getMapRef(T::mechanism_type::isInternal) + .emplace(mechName.toString(), std::make_unique<T>()) + .second); + return true; + } + +private: + stdx::unordered_map<std::string, std::unique_ptr<ServerFactoryBase>>& _getMapRef( + StringData dbName) { + return _getMapRef(dbName != "$external"_sd); + } + + stdx::unordered_map<std::string, std::unique_ptr<ServerFactoryBase>>& _getMapRef( + bool internal) { + if (internal) { + return _internalMap; + } + return _externalMap; + } + + bool _mechanismSupportedByConfig(StringData mechName); + + // Stores factories which make mechanisms for all databases other than $external + stdx::unordered_map<std::string, std::unique_ptr<ServerFactoryBase>> _internalMap; + // Stores factories which make mechanisms exclusively for $external + stdx::unordered_map<std::string, std::unique_ptr<ServerFactoryBase>> _externalMap; +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_mechanism_registry_test.cpp b/src/mongo/db/auth/sasl_mechanism_registry_test.cpp new file mode 100644 index 00000000000..8c8515adb43 --- /dev/null +++ b/src/mongo/db/auth/sasl_mechanism_registry_test.cpp @@ -0,0 +1,350 @@ +/** + * Copyright (C) 2018 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/db/auth/sasl_mechanism_registry.h" +#include "mongo/crypto/mechanism_scram.h" +#include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authz_manager_external_state_mock.h" +#include "mongo/db/operation_context_noop.h" +#include "mongo/db/service_context_noop.h" +#include "mongo/unittest/unittest.h" + +namespace mongo { +namespace { + + +TEST(SecurityProperty, emptyHasEmptyProperties) { + SecurityPropertySet set(SecurityPropertySet{}); + ASSERT_TRUE(set.hasAllProperties(set)); + ASSERT_FALSE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kMutualAuth})); + ASSERT_FALSE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText})); +} + +TEST(SecurityProperty, mutualHasMutalAndEmptyProperties) { + SecurityPropertySet set(SecurityPropertySet{SecurityProperty::kMutualAuth}); + ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{})); + ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kMutualAuth})); + ASSERT_FALSE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText})); +} + +TEST(SecurityProperty, mutualAndPlainHasAllSubsets) { + SecurityPropertySet set{SecurityProperty::kMutualAuth, SecurityProperty::kNoPlainText}; + ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{})); + ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kMutualAuth})); + ASSERT_TRUE(set.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText})); + ASSERT_TRUE(set.hasAllProperties( + SecurityPropertySet{SecurityProperty::kMutualAuth, SecurityProperty::kNoPlainText})); +} + +// Policy for a hypothetical "FOO" SASL mechanism. +struct FooPolicy { + static constexpr StringData getName() { + return "FOO"_sd; + } + + // This mech is kind of dangerous, it sends plaintext passwords across the wire. + static SecurityPropertySet getProperties() { + return SecurityPropertySet{SecurityProperty::kMutualAuth}; + } +}; + +template <bool argIsInternal> +class FooMechanism : public MakeServerMechanism<FooPolicy> { +public: + static const bool isInternal = argIsInternal; + explicit FooMechanism(std::string authenticationDatabase) + : MakeServerMechanism<FooPolicy>(std::move(authenticationDatabase)) {} + +protected: + StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx, + StringData input) final { + return std::make_tuple(true, std::string()); + } +}; + +template <bool argIsInternal> +class FooMechanismFactory : public MakeServerFactory<FooMechanism<argIsInternal>> { +public: + bool canMakeMechanismForUser(const User* user) const final { + return true; + } +}; + +// Policy for a hypothetical "BAR" SASL mechanism. +struct BarPolicy { + static constexpr StringData getName() { + return "BAR"_sd; + } + + static SecurityPropertySet getProperties() { + return SecurityPropertySet{SecurityProperty::kMutualAuth, SecurityProperty::kNoPlainText}; + } +}; + +template <bool argIsInternal> +class BarMechanism : public MakeServerMechanism<BarPolicy> { +public: + static const bool isInternal = argIsInternal; + explicit BarMechanism(std::string authenticationDatabase) + : MakeServerMechanism<BarPolicy>(std::move(authenticationDatabase)) {} + +protected: + StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx, + StringData input) final { + return std::make_tuple(true, std::string()); + } +}; + +template <bool argIsInternal> +class BarMechanismFactory : public MakeServerFactory<BarMechanism<argIsInternal>> { +public: + bool canMakeMechanismForUser(const User* user) const final { + return true; + } +}; + + +class MechanismRegistryTest : public mongo::unittest::Test { +public: + MechanismRegistryTest() + : opClient(serviceContext.makeClient("mechanismRegistryTest")), + opCtx(serviceContext.makeOperationContext(opClient.get())), + authManagerExternalState(new AuthzManagerExternalStateMock()), + authManager(new AuthorizationManager( + std::unique_ptr<AuthzManagerExternalStateMock>(authManagerExternalState))) { + AuthorizationManager::set(&serviceContext, + std::unique_ptr<AuthorizationManager>(authManager)); + + ASSERT_OK(authManagerExternalState->updateOne( + opCtx.get(), + AuthorizationManager::versionCollectionNamespace, + AuthorizationManager::versionDocumentQuery, + BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName + << AuthorizationManager::schemaVersion26Final)), + true, + BSONObj())); + + ASSERT_OK(authManagerExternalState->insert( + opCtx.get(), + NamespaceString("admin.system.users"), + BSON("_id" + << "test.sajack" + << "user" + << "sajack" + << "db" + << "test" + << "credentials" + << BSON("SCRAM-SHA-256" + << scram::Secrets<SHA256Block>::generateCredentials("sajack", 15000)) + << "roles" + << BSONArray()), + BSONObj())); + + + ASSERT_OK(authManagerExternalState->insert(opCtx.get(), + NamespaceString("admin.system.users"), + BSON("_id" + << "$external.sajack" + << "user" + << "sajack" + << "db" + << "$external" + << "credentials" + << BSON("external" << true) + << "roles" + << BSONArray()), + BSONObj())); + + + ASSERT_OK(authManagerExternalState->insert( + opCtx.get(), + NamespaceString("admin.system.users"), + BSON("_id" + << "test.collision" + << "user" + << "collision" + << "db" + << "test" + << "credentials" + << BSON("SCRAM-SHA-256" + << scram::Secrets<SHA256Block>::generateCredentials("collision", 15000)) + << "roles" + << BSONArray()), + BSONObj())); + + // A user whose name does not equal "test.collision"'s, but ends in a Zero Width Joiner. + ASSERT_OK(authManagerExternalState->insert( + opCtx.get(), + NamespaceString("admin.system.users"), + BSON("_id" + << "test.collision" // This string ends in a ZWJ + << "user" + << "collision" // This string ends in a ZWJ + << "db" + << "test" + << "credentials" + << BSON("SCRAM-SHA-256" + << scram::Secrets<SHA256Block>::generateCredentials("collision", 15000)) + << "roles" + << BSONArray()), + BSONObj())); + } + + ServiceContextNoop serviceContext; + ServiceContext::UniqueClient opClient; + ServiceContext::UniqueOperationContext opCtx; + AuthzManagerExternalStateMock* authManagerExternalState; + AuthorizationManager* authManager; + + SASLServerMechanismRegistry registry; +}; + +TEST_F(MechanismRegistryTest, acquireInternalMechanism) { + registry.registerFactory<FooMechanismFactory<true>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + auto swMechanism = registry.getServerMechanism(FooPolicy::getName(), "test"); + ASSERT_OK(swMechanism.getStatus()); +} + +TEST_F(MechanismRegistryTest, cantAcquireInternalMechanismOnExternal) { + registry.registerFactory<FooMechanismFactory<true>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + auto swMechanism = registry.getServerMechanism(FooPolicy::getName(), "$external"); + ASSERT_NOT_OK(swMechanism.getStatus()); +} + +TEST_F(MechanismRegistryTest, acquireExternalMechanism) { + registry.registerFactory<FooMechanismFactory<false>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + auto swMechanism = registry.getServerMechanism(FooPolicy::getName(), "$external"); + ASSERT_OK(swMechanism.getStatus()); +} + +TEST_F(MechanismRegistryTest, cantAcquireExternalMechanismOnInternal) { + registry.registerFactory<FooMechanismFactory<false>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + auto swMechanism = registry.getServerMechanism(FooPolicy::getName(), "test"); + ASSERT_NOT_OK(swMechanism.getStatus()); +} + + +TEST_F(MechanismRegistryTest, invalidUserCantAdvertiseMechs) { + registry.registerFactory<FooMechanismFactory<true>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + BSONObjBuilder builder; + + ASSERT_THROWS( + registry.advertiseMechanismNamesForUser(opCtx.get(), + BSON("isMaster" << 1 << "saslSupportedMechs" + << "test.noSuchUser"), + &builder), + AssertionException); +} + +TEST_F(MechanismRegistryTest, collisionsPreventAdvertisement) { + registry.registerFactory<FooMechanismFactory<true>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + BSONObjBuilder builder; + + registry.advertiseMechanismNamesForUser(opCtx.get(), + BSON("isMaster" << 1 << "saslSupportedMechs" + << "test.collision"), + &builder); + ASSERT_THROWS( + registry.advertiseMechanismNamesForUser(opCtx.get(), + BSON("isMaster" << 1 << "saslSupportedMechs" + << "test.collision"), + &builder), + AssertionException); +} + +TEST_F(MechanismRegistryTest, strongMechCanAdvertise) { + registry.registerFactory<BarMechanismFactory<true>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + registry.registerFactory<BarMechanismFactory<false>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + BSONObjBuilder builder; + registry.advertiseMechanismNamesForUser(opCtx.get(), + BSON("isMaster" << 1 << "saslSupportedMechs" + << "test.sajack"), + &builder); + + BSONObj obj = builder.done(); + ASSERT_BSONOBJ_EQ(BSON("saslSupportedMechs" << BSON_ARRAY("BAR")), obj); + + BSONObjBuilder builderExternal; + registry.advertiseMechanismNamesForUser(opCtx.get(), + BSON("isMaster" << 1 << "saslSupportedMechs" + << "$external.sajack"), + &builderExternal); + + BSONObj objExternal = builderExternal.done(); + ASSERT_BSONOBJ_EQ(BSON("saslSupportedMechs" << BSON_ARRAY("BAR")), objExternal); +} + +TEST_F(MechanismRegistryTest, weakMechCannotAdvertiseOnInternal) { + registry.registerFactory<FooMechanismFactory<true>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + BSONObjBuilder builder; + registry.advertiseMechanismNamesForUser(opCtx.get(), + BSON("isMaster" << 1 << "saslSupportedMechs" + << "test.sajack"), + &builder); + + + BSONObj obj = builder.done(); + + ASSERT_BSONOBJ_EQ(BSON("saslSupportedMechs" << BSONArray()), obj); +} + +TEST_F(MechanismRegistryTest, weakMechCanAdvertiseOnExternal) { + registry.registerFactory<FooMechanismFactory<false>>( + SASLServerMechanismRegistry::kNoValidateGlobalMechanisms); + + BSONObjBuilder builder; + registry.advertiseMechanismNamesForUser(opCtx.get(), + BSON("isMaster" << 1 << "saslSupportedMechs" + << "$external.sajack"), + &builder); + + BSONObj obj = builder.done(); + + ASSERT_BSONOBJ_EQ(BSON("saslSupportedMechs" << BSON_ARRAY("FOO")), obj); +} + + +} // namespace +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.cpp b/src/mongo/db/auth/sasl_plain_server_conversation.cpp index a21686f4af0..3c32c9b7f58 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_plain_server_conversation.cpp @@ -26,10 +26,18 @@ * it in the license file. */ +#include "mongo/platform/basic.h" + +#include <string> + #include "mongo/db/auth/sasl_plain_server_conversation.h" +#include "mongo/base/init.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" #include "mongo/crypto/mechanism_scram.h" -#include "mongo/db/auth/sasl_authentication_session.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" +#include "mongo/db/auth/user.h" #include "mongo/util/base64.h" #include "mongo/util/password_digest.h" #include "mongo/util/text.h" @@ -61,17 +69,15 @@ StatusWith<bool> trySCRAM(const User::CredentialData& credentials, StringData pw } } // namespace -SaslPLAINServerConversation::SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession) - : SaslServerConversation(saslAuthSession) {} - -SaslPLAINServerConversation::~SaslPLAINServerConversation(){}; - -StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::string* outputData) { - if (_saslAuthSession->getAuthenticationDatabase() == "$external") { +StatusWith<std::tuple<bool, std::string>> SASLPlainServerMechanism::stepImpl( + OperationContext* opCtx, StringData inputData) { + if (_authenticationDatabase == "$external") { return Status(ErrorCodes::AuthenticationFailed, "PLAIN mechanism must be used with internal users"); } + AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext()); + // Expecting user input on the form: [authz-id]\0authn-id\0pwd std::string input = inputData.toString(); @@ -93,12 +99,14 @@ StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::st } std::string authorizationIdentity = input.substr(0, firstNull); - _user = input.substr(firstNull + 1, (secondNull - firstNull) - 1); - if (_user.empty()) { + ServerMechanismBase::_principalName = + input.substr(firstNull + 1, (secondNull - firstNull) - 1); + if (ServerMechanismBase::_principalName.empty()) { return Status(ErrorCodes::AuthenticationFailed, str::stream() << "Incorrectly formatted PLAIN client message, empty username"); - } else if (!authorizationIdentity.empty() && authorizationIdentity != _user) { + } else if (!authorizationIdentity.empty() && + authorizationIdentity != ServerMechanismBase::_principalName) { return Status(ErrorCodes::AuthenticationFailed, str::stream() << "SASL authorization identity must match authentication identity"); @@ -116,38 +124,45 @@ StatusWith<bool> SaslPLAINServerConversation::step(StringData inputData, std::st User* userObj; // The authentication database is also the source database for the user. - Status status = - _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().acquireUser( - _saslAuthSession->getOpCtxt(), - UserName(_user, _saslAuthSession->getAuthenticationDatabase()), - &userObj); + Status status = authManager->acquireUser( + opCtx, UserName(ServerMechanismBase::_principalName, _authenticationDatabase), &userObj); if (!status.isOK()) { return status; } const auto creds = userObj->getCredentials(); - _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().releaseUser(userObj); + authManager->releaseUser(userObj); - *outputData = ""; const auto sha256Status = trySCRAM<SHA256Block>(creds, pwd->c_str()); if (!sha256Status.isOK()) { - return sha256Status; + return sha256Status.getStatus(); } if (sha256Status.getValue()) { - return true; + return std::make_tuple(true, std::string()); } - const auto authDigest = createPasswordDigest(_user, pwd->c_str()); + const auto authDigest = createPasswordDigest(ServerMechanismBase::_principalName, pwd->c_str()); const auto sha1Status = trySCRAM<SHA1Block>(creds, authDigest); if (!sha1Status.isOK()) { - return sha1Status; + return sha1Status.getStatus(); } if (sha1Status.getValue()) { - return true; + return std::make_tuple(true, std::string()); } return Status(ErrorCodes::AuthenticationFailed, str::stream() << "No credentials available."); + + + return std::make_tuple(true, std::string()); +} + +MONGO_INITIALIZER_WITH_PREREQUISITES(SASLPLAINServerMechanism, + ("CreateSASLServerMechanismRegistry")) +(::mongo::InitializerContext* context) { + auto& registry = SASLServerMechanismRegistry::get(getGlobalServiceContext()); + registry.registerFactory<PLAINServerFactory>(); + return Status::OK(); } } // namespace mongo diff --git a/src/mongo/db/auth/sasl_plain_server_conversation.h b/src/mongo/db/auth/sasl_plain_server_conversation.h index 5d3b57ffa89..13d7003c65c 100644 --- a/src/mongo/db/auth/sasl_plain_server_conversation.h +++ b/src/mongo/db/auth/sasl_plain_server_conversation.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 MongoDB Inc. + * Copyright (C) 2018 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, @@ -28,30 +28,31 @@ #pragma once -#include <string> - -#include "mongo/base/disallow_copying.h" -#include "mongo/base/status.h" -#include "mongo/base/string_data.h" -#include "mongo/db/auth/sasl_server_conversation.h" +#include "mongo/db/auth/sasl_mechanism_policies.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" namespace mongo { -/** - * Server side authentication session for SASL PLAIN. - */ -class SaslPLAINServerConversation : public SaslServerConversation { - MONGO_DISALLOW_COPYING(SaslPLAINServerConversation); +class SASLPlainServerMechanism : public MakeServerMechanism<PLAINPolicy> { public: - /** - * Implements the server side of a SASL PLAIN mechanism session. - * - **/ - explicit SaslPLAINServerConversation(SaslAuthenticationSession* saslAuthSession); + static const bool isInternal = true; + + explicit SASLPlainServerMechanism(std::string authenticationDatabase) + : MakeServerMechanism<PLAINPolicy>(std::move(authenticationDatabase)) {} - virtual ~SaslPLAINServerConversation(); +private: + StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx, + StringData input) final; +}; - virtual StatusWith<bool> step(StringData inputData, std::string* outputData); +class PLAINServerFactory : public MakeServerFactory<SASLPlainServerMechanism> { +public: + bool canMakeMechanismForUser(const User* user) const final { + auto credentials = user->getCredentials(); + return !credentials.isExternal && (credentials.scram<SHA1Block>().isValid() || + credentials.scram<SHA256Block>().isValid()); + } }; + } // namespace mongo diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.cpp b/src/mongo/db/auth/sasl_scram_server_conversation.cpp index 3e5fc0676b6..65720d6d383 100644 --- a/src/mongo/db/auth/sasl_scram_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scram_server_conversation.cpp @@ -35,7 +35,14 @@ #include <boost/algorithm/string/join.hpp> #include <boost/algorithm/string/replace.hpp> +#include "mongo/base/disallow_copying.h" +#include "mongo/base/init.h" +#include "mongo/base/status.h" +#include "mongo/base/string_data.h" +#include "mongo/crypto/mechanism_scram.h" #include "mongo/crypto/sha1_block.h" +#include "mongo/db/auth/sasl_mechanism_policies.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" #include "mongo/db/auth/sasl_options.h" #include "mongo/platform/random.h" #include "mongo/util/base64.h" @@ -46,26 +53,25 @@ namespace mongo { -using std::unique_ptr; -using std::string; -StatusWith<bool> SaslSCRAMServerConversation::step(StringData inputData, std::string* outputData) { +template <typename Policy> +StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::stepImpl( + OperationContext* opCtx, StringData inputData) { _step++; if (_step > 3 || _step <= 0) { return Status(ErrorCodes::AuthenticationFailed, str::stream() << "Invalid SCRAM authentication step: " << _step); } + if (_step == 1) { - return _firstStep(inputData, outputData); + return _firstStep(opCtx, inputData); } if (_step == 2) { - return _secondStep(inputData, outputData); + return _secondStep(opCtx, inputData); } - *outputData = ""; - - return true; + return std::make_tuple(true, std::string{}); } /* @@ -86,8 +92,9 @@ static void decodeSCRAMUsername(std::string& user) { * * NOTE: we are ignoring the authorization ID part of the message */ -StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, - std::string* outputData) { +template <typename Policy> +StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_firstStep( + OperationContext* opCtx, StringData inputData) { const auto badCount = [](int got) { return Status(ErrorCodes::BadValue, str::stream() @@ -104,7 +111,7 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, * client-first-message := gs2-header client-first-message-bare */ const auto gs2_cbind_comma = inputData.find(','); - if (gs2_cbind_comma == string::npos) { + if (gs2_cbind_comma == std::string::npos) { return badCount(1); } const auto gs2_cbind_flag = inputData.substr(0, gs2_cbind_comma); @@ -118,7 +125,7 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, } const auto gs2_header_comma = inputData.find(',', gs2_cbind_comma + 1); - if (gs2_header_comma == string::npos) { + if (gs2_header_comma == std::string::npos) { return badCount(2); } auto authzId = inputData.substr(gs2_cbind_comma + 1, gs2_header_comma - (gs2_cbind_comma + 1)); @@ -155,18 +162,19 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, return Status(ErrorCodes::BadValue, str::stream() << "Invalid SCRAM user name: " << input[0]); } - _user = input[0].substr(2); - decodeSCRAMUsername(_user); + ServerMechanismBase::_principalName = input[0].substr(2); + decodeSCRAMUsername(ServerMechanismBase::_principalName); - auto swUser = saslPrep(_user); + auto swUser = saslPrep(ServerMechanismBase::ServerMechanismBase::_principalName); if (!swUser.isOK()) { return swUser.getStatus(); } - _user = std::move(swUser.getValue()); + ServerMechanismBase::ServerMechanismBase::_principalName = std::move(swUser.getValue()); - if (!authzId.empty() && _user != authzId) { + if (!authzId.empty() && ServerMechanismBase::_principalName != authzId) { return Status(ErrorCodes::BadValue, - str::stream() << "SCRAM user name " << _user << " does not match authzid " + str::stream() << "SCRAM user name " << ServerMechanismBase::_principalName + << " does not match authzid " << authzId); } @@ -180,7 +188,8 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, // SERVER-16534, SCRAM-SHA-1 must be enabled for authenticating the internal user, so that // cluster members may communicate with each other. Hence ignore disabled auth mechanism // for the internal user. - UserName user(_user, _saslAuthSession->getAuthenticationDatabase()); + UserName user(ServerMechanismBase::ServerMechanismBase::_principalName, + ServerMechanismBase::getAuthenticationDatabase()); if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && user != internalSecurity.user->getName()) { return Status(ErrorCodes::BadValue, "SCRAM-SHA-1 authentication is disabled"); @@ -188,20 +197,21 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, // The authentication database is also the source database for the user. User* userObj; - Status status = - _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().acquireUser( - _saslAuthSession->getOpCtxt(), user, &userObj); + auto authManager = AuthorizationManager::get(opCtx->getServiceContext()); + Status status = authManager->acquireUser(opCtx, user, &userObj); if (!status.isOK()) { return status; } - _creds = userObj->getCredentials(); + User::CredentialData credentials = userObj->getCredentials(); UserName userName = userObj->getName(); - _saslAuthSession->getAuthorizationSession()->getAuthorizationManager().releaseUser(userObj); + authManager->releaseUser(userObj); - if (!initAndValidateCredentials()) { + _scramCredentials = credentials.scram<HashBlock>(); + + if (!_scramCredentials.isValid()) { // Check for authentication attempts of the __system user on // systems started without a keyfile. if (userName == internalSecurity.user->getName()) { @@ -215,12 +225,16 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, } } + _secrets = scram::Secrets<HashBlock>("", + base64::decode(_scramCredentials.storedKey), + base64::decode(_scramCredentials.serverKey)); + // Generate server-first-message // Create text-based nonce as base64 encoding of a binary blob of length multiple of 3 const int nonceLenQWords = 3; uint64_t binaryNonce[nonceLenQWords]; - unique_ptr<SecureRandom> sr(SecureRandom::create()); + std::unique_ptr<SecureRandom> sr(SecureRandom::create()); binaryNonce[0] = sr->nextInt64(); binaryNonce[1] = sr->nextInt64(); @@ -229,13 +243,14 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, _nonce = clientNonce + base64::encode(reinterpret_cast<char*>(binaryNonce), sizeof(binaryNonce)); StringBuilder sb; - sb << "r=" << _nonce << ",s=" << getSalt() << ",i=" << getIterationCount(); - *outputData = sb.str(); + sb << "r=" << _nonce << ",s=" << _scramCredentials.salt + << ",i=" << _scramCredentials.iterationCount; + std::string outputData = sb.str(); // add client-first-message-bare and server-first-message to _authMessage - _authMessage = client_first_message_bare.toString() + "," + *outputData; + _authMessage = client_first_message_bare.toString() + "," + outputData; - return false; + return std::make_tuple(false, std::move(outputData)); } /** @@ -250,8 +265,9 @@ StatusWith<bool> SaslSCRAMServerConversation::_firstStep(StringData inputData, * * NOTE: we are ignoring the channel binding part of the message **/ -StatusWith<bool> SaslSCRAMServerConversation::_secondStep(StringData inputData, - std::string* outputData) { +template <typename Policy> +StatusWith<std::tuple<bool, std::string>> SaslSCRAMServerMechanism<Policy>::_secondStep( + OperationContext* opCtx, StringData inputData) { const auto badCount = [](int got) { return Status(ErrorCodes::BadValue, str::stream() @@ -265,7 +281,7 @@ StatusWith<bool> SaslSCRAMServerConversation::_secondStep(StringData inputData, * client-final-message := client-final-message-without-proof ',' proof */ const auto last_comma = inputData.rfind(','); - if (last_comma == string::npos) { + if (last_comma == std::string::npos) { return badCount(1); } @@ -315,18 +331,26 @@ StatusWith<bool> SaslSCRAMServerConversation::_secondStep(StringData inputData, // ClientSignature := HMAC(StoredKey, AuthMessage) // ClientKey := ClientSignature XOR ClientProof // ServerSignature := HMAC(ServerKey, AuthMessage) - invariant(initAndValidateCredentials()); - if (!verifyClientProof(base64::decode(proof.toString()))) { + if (!_secrets.verifyClientProof(_authMessage, base64::decode(proof.toString()))) { return Status(ErrorCodes::AuthenticationFailed, "SCRAM authentication failed, storedKey mismatch"); } StringBuilder sb; // ServerSignature := HMAC(ServerKey, AuthMessage) - sb << "v=" << generateServerSignature(); - *outputData = sb.str(); + sb << "v=" << _secrets.generateServerSignature(_authMessage); - return false; + return std::make_tuple(false, sb.str()); } + +MONGO_INITIALIZER_WITH_PREREQUISITES(SASLSCRAMServerMechanism, + ("CreateSASLServerMechanismRegistry")) +(::mongo::InitializerContext* context) { + auto& registry = SASLServerMechanismRegistry::get(getGlobalServiceContext()); + registry.registerFactory<SCRAMSHA1ServerFactory>(); + registry.registerFactory<SCRAMSHA256ServerFactory>(); + return Status::OK(); +} + } // namespace mongo diff --git a/src/mongo/db/auth/sasl_scram_server_conversation.h b/src/mongo/db/auth/sasl_scram_server_conversation.h index 718e168feea..33713258028 100644 --- a/src/mongo/db/auth/sasl_scram_server_conversation.h +++ b/src/mongo/db/auth/sasl_scram_server_conversation.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2014 MongoDB Inc. + * Copyright (C) 2018 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, @@ -28,28 +28,26 @@ #pragma once -#include <string> -#include <vector> - -#include "mongo/base/disallow_copying.h" -#include "mongo/base/status.h" -#include "mongo/base/status_with.h" -#include "mongo/base/string_data.h" #include "mongo/crypto/mechanism_scram.h" -#include "mongo/db/auth/sasl_server_conversation.h" +#include "mongo/db/auth/sasl_mechanism_policies.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" #include "mongo/util/icu.h" namespace mongo { + /** * Server side authentication session for SASL SCRAM-SHA-1. */ -class SaslSCRAMServerConversation : public SaslServerConversation { - MONGO_DISALLOW_COPYING(SaslSCRAMServerConversation); - +template <typename Policy> +class SaslSCRAMServerMechanism : public MakeServerMechanism<Policy> { public: - explicit SaslSCRAMServerConversation(SaslAuthenticationSession* session) - : SaslServerConversation(session) {} - ~SaslSCRAMServerConversation() override = default; + using HashBlock = typename Policy::HashBlock; + static const bool isInternal = true; + + explicit SaslSCRAMServerMechanism(std::string authenticationDatabase) + : MakeServerMechanism<Policy>(std::move(authenticationDatabase)) {} + + ~SaslSCRAMServerMechanism() final = default; /** * Take one step in a SCRAM-SHA-1 conversation. @@ -58,105 +56,51 @@ public: * authentication conversation is finished or not. * **/ - StatusWith<bool> step(StringData inputData, std::string* outputData) override; - - /** - * Initialize details, called after _creds has been loaded. - */ - virtual bool initAndValidateCredentials() = 0; - - /** - * Provide the predetermined salt to the client. - */ - virtual std::string getSalt() const = 0; - - /** - * Provide the predetermined iteration count to the client. - */ - virtual size_t getIterationCount() const = 0; + StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx, + StringData inputData); - /** - * Verify proof submitted by authenticating client. - */ - virtual bool verifyClientProof(StringData) const = 0; - - /** - * Generate a signature to prove ourselves. - */ - virtual std::string generateServerSignature() const = 0; - - /** - * Runs saslPrep except on SHA-1. - */ - virtual StatusWith<std::string> saslPrep(StringData str) const = 0; + StatusWith<std::string> saslPrep(StringData str) const { + if (std::is_same<SHA1Block, HashBlock>::value) { + return str.toString(); + } else { + return mongo::saslPrep(str); + } + } private: /** * Parse client-first-message and generate server-first-message **/ - StatusWith<bool> _firstStep(StringData input, std::string* outputData); + StatusWith<std::tuple<bool, std::string>> _firstStep(OperationContext* opCtx, StringData input); /** * Parse client-final-message and generate server-final-message **/ - StatusWith<bool> _secondStep(StringData input, std::string* outputData); + StatusWith<std::tuple<bool, std::string>> _secondStep(OperationContext* opCtx, + StringData input); -protected: int _step{0}; std::string _authMessage; - User::CredentialData _creds; + User::SCRAMCredentials<HashBlock> _scramCredentials; + scram::Secrets<HashBlock> _secrets; // client and server nonce concatenated std::string _nonce; }; -template <typename HashBlock> -class SaslSCRAMServerConversationImpl : public SaslSCRAMServerConversation { +template <typename ScramMechanism> +class SCRAMServerFactory : public MakeServerFactory<ScramMechanism> { public: - explicit SaslSCRAMServerConversationImpl(SaslAuthenticationSession* session) - : SaslSCRAMServerConversation(session) {} - ~SaslSCRAMServerConversationImpl() override = default; - - bool initAndValidateCredentials() final { - const auto& scram = _creds.scram<HashBlock>(); - if (!scram.isValid()) { - return false; - } - if (!_credentials) { - _credentials = scram::Secrets<HashBlock>( - "", base64::decode(scram.storedKey), base64::decode(scram.serverKey)); - } - return true; - } - - std::string getSalt() const final { - return _creds.scram<HashBlock>().salt; - } - - size_t getIterationCount() const final { - return _creds.scram<HashBlock>().iterationCount; - } - - bool verifyClientProof(StringData clientProof) const final { - return _credentials.verifyClientProof(_authMessage, clientProof); - } - - std::string generateServerSignature() const final { - return _credentials.generateServerSignature(_authMessage); + bool canMakeMechanismForUser(const User* user) const final { + auto credentials = user->getCredentials(); + return credentials.scram<typename ScramMechanism::HashBlock>().isValid(); } - - StatusWith<std::string> saslPrep(StringData str) const final { - if (std::is_same<SHA1Block, HashBlock>::value) { - return str.toString(); - } else { - return mongo::saslPrep(str); - } - } - -private: - scram::Secrets<HashBlock> _credentials; }; -using SaslSCRAMSHA1ServerConversation = SaslSCRAMServerConversationImpl<SHA1Block>; +using SaslSCRAMSHA1ServerMechanism = SaslSCRAMServerMechanism<SCRAMSHA1Policy>; +using SCRAMSHA1ServerFactory = SCRAMServerFactory<SaslSCRAMSHA1ServerMechanism>; + +using SaslSCRAMSHA256ServerMechanism = SaslSCRAMServerMechanism<SCRAMSHA256Policy>; +using SCRAMSHA256ServerFactory = SCRAMServerFactory<SaslSCRAMSHA256ServerMechanism>; } // namespace mongo diff --git a/src/mongo/db/auth/sasl_scram_test.cpp b/src/mongo/db/auth/sasl_scram_test.cpp index c75cae5f260..3ec3cb5c5d9 100644 --- a/src/mongo/db/auth/sasl_scram_test.cpp +++ b/src/mongo/db/auth/sasl_scram_test.cpp @@ -36,9 +36,10 @@ #include "mongo/crypto/sha1_block.h" #include "mongo/crypto/sha256_block.h" #include "mongo/db/auth/authorization_manager.h" +#include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/authz_manager_external_state_mock.h" #include "mongo/db/auth/authz_session_external_state_mock.h" -#include "mongo/db/auth/native_sasl_authentication_session.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" #include "mongo/db/auth/sasl_scram_server_conversation.h" #include "mongo/db/service_context_noop.h" #include "mongo/stdx/memory.h" @@ -166,75 +167,39 @@ struct SCRAMStepsResult { } }; -SCRAMStepsResult runSteps(NativeSaslAuthenticationSession* saslServerSession, - NativeSaslClientSession* saslClientSession, - SCRAMMutators interposers = SCRAMMutators{}) { - SCRAMStepsResult result{}; - std::string clientOutput = ""; - std::string serverOutput = ""; - - for (size_t step = 1; step <= 3; step++) { - ASSERT_FALSE(saslClientSession->isDone()); - ASSERT_FALSE(saslServerSession->isDone()); - - // Client step - result.status = saslClientSession->step(serverOutput, &clientOutput); - if (result.status != Status::OK()) { - return result; - } - interposers.execute(result.outcome, clientOutput); - std::cout << result.outcome.toString() << ": " << clientOutput << std::endl; - result.outcome.next(); - - // Server step - result.status = saslServerSession->step(clientOutput, &serverOutput); - if (result.status != Status::OK()) { - return result; - } - interposers.execute(result.outcome, serverOutput); - std::cout << result.outcome.toString() << ": " << serverOutput << std::endl; - result.outcome.next(); - } - ASSERT_TRUE(saslClientSession->isDone()); - ASSERT_TRUE(saslServerSession->isDone()); - - return result; -} - class SCRAMFixture : public mongo::unittest::Test { protected: const SCRAMStepsResult goalState = SCRAMStepsResult(SaslTestState(SaslTestState::kClient, 4), Status::OK()); - ServiceContextNoop serviceContext; + std::unique_ptr<ServiceContextNoop> serviceContext; ServiceContextNoop::UniqueClient client; ServiceContextNoop::UniqueOperationContext opCtx; AuthzManagerExternalStateMock* authzManagerExternalState; - std::unique_ptr<AuthorizationManager> authzManager; + AuthorizationManager* authzManager; std::unique_ptr<AuthorizationSession> authzSession; - std::unique_ptr<NativeSaslAuthenticationSession> saslServerSession; + std::unique_ptr<ServerMechanismBase> saslServerSession; std::unique_ptr<NativeSaslClientSession> saslClientSession; void setUp() final { - client = serviceContext.makeClient("test"); - opCtx = serviceContext.makeOperationContext(client.get()); + serviceContext = stdx::make_unique<ServiceContextNoop>(); + client = serviceContext->makeClient("test"); + opCtx = serviceContext->makeOperationContext(client.get()); auto uniqueAuthzManagerExternalStateMock = stdx::make_unique<AuthzManagerExternalStateMock>(); authzManagerExternalState = uniqueAuthzManagerExternalStateMock.get(); - authzManager = - stdx::make_unique<AuthorizationManager>(std::move(uniqueAuthzManagerExternalStateMock)); + authzManager = new AuthorizationManager(std::move(uniqueAuthzManagerExternalStateMock)); authzSession = stdx::make_unique<AuthorizationSession>( - stdx::make_unique<AuthzSessionExternalStateMock>(authzManager.get())); + stdx::make_unique<AuthzSessionExternalStateMock>(authzManager)); + AuthorizationManager::set(serviceContext.get(), + std::unique_ptr<AuthorizationManager>(authzManager)); - saslServerSession = stdx::make_unique<NativeSaslAuthenticationSession>(authzSession.get()); - saslServerSession->setOpCtxt(opCtx.get()); - ASSERT_OK( - saslServerSession->start("test", _mechanism, "mongodb", "MockServer.test", 1, false)); saslClientSession = stdx::make_unique<NativeSaslClientSession>(); - saslClientSession->setParameter(NativeSaslClientSession::parameterMechanism, _mechanism); + saslClientSession->setParameter(NativeSaslClientSession::parameterMechanism, + saslServerSession->mechanismName()); saslClientSession->setParameter(NativeSaslClientSession::parameterServiceName, "mongodb"); saslClientSession->setParameter(NativeSaslClientSession::parameterServiceHostname, "MockServer.test"); @@ -243,13 +208,15 @@ protected: } void tearDown() final { + opCtx.reset(); + client.reset(); + serviceContext.reset(); + saslClientSession.reset(); saslServerSession.reset(); + authzSession.reset(); - authzManager.reset(); authzManagerExternalState = nullptr; - opCtx.reset(); - client.reset(); } std::string createPasswordDigest(StringData username, StringData password) { @@ -260,18 +227,55 @@ protected: } } - std::string _mechanism; + SCRAMStepsResult runSteps(SCRAMMutators interposers = SCRAMMutators{}) { + SCRAMStepsResult result{}; + std::string clientOutput = ""; + std::string serverOutput = ""; + + for (size_t step = 1; step <= 3; step++) { + ASSERT_FALSE(saslClientSession->isDone()); + ASSERT_FALSE(saslServerSession->isDone()); + + // Client step + result.status = saslClientSession->step(serverOutput, &clientOutput); + if (result.status != Status::OK()) { + return result; + } + interposers.execute(result.outcome, clientOutput); + std::cout << result.outcome.toString() << ": " << clientOutput << std::endl; + result.outcome.next(); + + // Server step + StatusWith<std::string> swServerResult = + saslServerSession->step(opCtx.get(), clientOutput); + result.status = swServerResult.getStatus(); + if (result.status != Status::OK()) { + return result; + } + serverOutput = std::move(swServerResult.getValue()); + + interposers.execute(result.outcome, serverOutput); + std::cout << result.outcome.toString() << ": " << serverOutput << std::endl; + result.outcome.next(); + } + ASSERT_TRUE(saslClientSession->isDone()); + ASSERT_TRUE(saslServerSession->isDone()); + + return result; + } + + bool _digestPassword; public: void run() { log() << "SCRAM-SHA-1 variant"; - _mechanism = "SCRAM-SHA-1"; + saslServerSession = std::make_unique<SaslSCRAMSHA1ServerMechanism>("test"); _digestPassword = true; Test::run(); log() << "SCRAM-SHA-256 variant"; - _mechanism = "SCRAM-SHA-256"; + saslServerSession = std::make_unique<SaslSCRAMSHA256ServerMechanism>("test"); _digestPassword = false; Test::run(); } @@ -297,7 +301,7 @@ TEST_F(SCRAMFixture, testServerStep1DoesNotIncludeNonceFromClientStep1) { ASSERT_EQ( SCRAMStepsResult(SaslTestState(SaslTestState::kClient, 2), Status(ErrorCodes::BadValue, "Incorrect SCRAM client|server nonce: r=")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); + runSteps(mutator)); } TEST_F(SCRAMFixture, testClientStep2DoesNotIncludeNonceFromServerStep1) { @@ -319,7 +323,7 @@ TEST_F(SCRAMFixture, testClientStep2DoesNotIncludeNonceFromServerStep1) { ASSERT_EQ( SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), Status(ErrorCodes::BadValue, "Incorrect SCRAM client|server nonce: r=")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); + runSteps(mutator)); } TEST_F(SCRAMFixture, testClientStep2GivesBadProof) { @@ -345,7 +349,7 @@ TEST_F(SCRAMFixture, testClientStep2GivesBadProof) { Status(ErrorCodes::AuthenticationFailed, "SCRAM authentication failed, storedKey mismatch")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); + runSteps(mutator)); } TEST_F(SCRAMFixture, testServerStep2GivesBadVerifier) { @@ -371,7 +375,7 @@ TEST_F(SCRAMFixture, testServerStep2GivesBadVerifier) { }); - auto result = runSteps(saslServerSession.get(), saslClientSession.get(), mutator); + auto result = runSteps(mutator); ASSERT_EQ(SCRAMStepsResult( SaslTestState(SaslTestState::kClient, 3), @@ -392,7 +396,7 @@ TEST_F(SCRAMFixture, testSCRAM) { ASSERT_OK(saslClientSession->initialize()); - ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); + ASSERT_EQ(goalState, runSteps()); } TEST_F(SCRAMFixture, testSCRAMWithChannelBindingSupportedByClient) { @@ -410,7 +414,7 @@ TEST_F(SCRAMFixture, testSCRAMWithChannelBindingSupportedByClient) { clientMessage.replace(clientMessage.begin(), clientMessage.begin() + 1, "y"); }); - ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); + ASSERT_EQ(goalState, runSteps(mutator)); } TEST_F(SCRAMFixture, testSCRAMWithChannelBindingRequiredByClient) { @@ -431,7 +435,7 @@ TEST_F(SCRAMFixture, testSCRAMWithChannelBindingRequiredByClient) { ASSERT_EQ( SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), Status(ErrorCodes::BadValue, "Server does not support channel binding")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); + runSteps(mutator)); } TEST_F(SCRAMFixture, testSCRAMWithInvalidChannelBinding) { @@ -452,7 +456,7 @@ TEST_F(SCRAMFixture, testSCRAMWithInvalidChannelBinding) { ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), Status(ErrorCodes::BadValue, "Incorrect SCRAM client message prefix: v=illegalGarbage")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); + runSteps(mutator)); } TEST_F(SCRAMFixture, testNULLInPassword) { @@ -465,7 +469,7 @@ TEST_F(SCRAMFixture, testNULLInPassword) { ASSERT_OK(saslClientSession->initialize()); - ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); + ASSERT_EQ(goalState, runSteps()); } @@ -479,7 +483,7 @@ TEST_F(SCRAMFixture, testCommasInUsernameAndPassword) { ASSERT_OK(saslClientSession->initialize()); - ASSERT_EQ(goalState, runSteps(saslServerSession.get(), saslClientSession.get())); + ASSERT_EQ(goalState, runSteps()); } TEST_F(SCRAMFixture, testIncorrectUser) { @@ -491,7 +495,7 @@ TEST_F(SCRAMFixture, testIncorrectUser) { ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 1), Status(ErrorCodes::UserNotFound, "Could not find user sajack@test")), - runSteps(saslServerSession.get(), saslClientSession.get())); + runSteps()); } TEST_F(SCRAMFixture, testIncorrectPassword) { @@ -507,7 +511,7 @@ TEST_F(SCRAMFixture, testIncorrectPassword) { ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), Status(ErrorCodes::AuthenticationFailed, "SCRAM authentication failed, storedKey mismatch")), - runSteps(saslServerSession.get(), saslClientSession.get())); + runSteps()); } TEST_F(SCRAMFixture, testOptionalClientExtensions) { @@ -531,7 +535,7 @@ TEST_F(SCRAMFixture, testOptionalClientExtensions) { ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), Status(ErrorCodes::AuthenticationFailed, "SCRAM authentication failed, storedKey mismatch")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); + runSteps(mutator)); } TEST_F(SCRAMFixture, testOptionalServerExtensions) { @@ -556,7 +560,7 @@ TEST_F(SCRAMFixture, testOptionalServerExtensions) { ASSERT_EQ(SCRAMStepsResult(SaslTestState(SaslTestState::kServer, 2), Status(ErrorCodes::AuthenticationFailed, "SCRAM authentication failed, storedKey mismatch")), - runSteps(saslServerSession.get(), saslClientSession.get(), mutator)); + runSteps(mutator)); } template <typename HashBlock> diff --git a/src/mongo/db/auth/sasl_server_conversation.cpp b/src/mongo/db/auth/sasl_server_conversation.cpp deleted file mode 100644 index 75f680e9a0d..00000000000 --- a/src/mongo/db/auth/sasl_server_conversation.cpp +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (C) 2014 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/db/auth/sasl_server_conversation.h" - -#include <string> - -namespace mongo { - -SaslServerConversation::~SaslServerConversation(){}; - -std::string SaslServerConversation::getPrincipalId() { - return _user; -} - -} // namespace mongo diff --git a/src/mongo/db/auth/sasl_server_conversation.h b/src/mongo/db/auth/sasl_server_conversation.h deleted file mode 100644 index 5d0ce497523..00000000000 --- a/src/mongo/db/auth/sasl_server_conversation.h +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2014 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/disallow_copying.h" -#include "mongo/base/status.h" -#include "mongo/base/string_data.h" -#include "mongo/db/auth/sasl_authentication_session.h" -#include "mongo/db/auth/user.h" - -namespace mongo { - -class SaslAuthenticationSession; -template <typename T> -class StatusWith; - -/** - * Abstract class for implementing the server-side - * of a SASL mechanism conversation. - */ -class SaslServerConversation { - MONGO_DISALLOW_COPYING(SaslServerConversation); - -public: - /** - * Implements the server side of a SASL authentication mechanism. - * - * "saslAuthSession" is the corresponding SASLAuthenticationSession. - * "saslAuthSession" must stay in scope until the SaslServerConversation's - * destructor completes. - * - **/ - explicit SaslServerConversation(SaslAuthenticationSession* saslAuthSession) - : _saslAuthSession(saslAuthSession), _user("") {} - - virtual ~SaslServerConversation(); - - /** - * Performs one step of the server side of the authentication session, - * consuming "inputData" and producing "*outputData". - * - * A return of Status::OK() indicates successful progress towards authentication. - * A return of !Status::OK() indicates failed authentication - * - * A return of true means that the authentication process has finished. - * A return of false means that the authentication process has more steps. - * - */ - virtual StatusWith<bool> step(StringData inputData, std::string* outputData) = 0; - - /** - * Gets the SASL principal id (user name) for the conversation - **/ - std::string getPrincipalId(); - -protected: - SaslAuthenticationSession* _saslAuthSession; - std::string _user; -}; - -} // namespace mongo diff --git a/src/mongo/db/repl/SConscript b/src/mongo/db/repl/SConscript index ab9c0dc2227..5cebfed131d 100644 --- a/src/mongo/db/repl/SConscript +++ b/src/mongo/db/repl/SConscript @@ -1522,6 +1522,7 @@ env.Library( '$BUILD_DIR/mongo/base', '$BUILD_DIR/mongo/client/clientdriver', '$BUILD_DIR/mongo/db/auth/authcore', + '$BUILD_DIR/mongo/db/auth/saslauth', '$BUILD_DIR/mongo/db/dbhelpers', '$BUILD_DIR/mongo/db/query_exec', 'oplog', diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp index 396c41ff260..fc33d735f13 100644 --- a/src/mongo/db/repl/replication_info.cpp +++ b/src/mongo/db/repl/replication_info.cpp @@ -33,7 +33,7 @@ #include <vector> #include "mongo/client/connpool.h" -#include "mongo/db/auth/sasl_mechanism_advertiser.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" #include "mongo/db/client.h" #include "mongo/db/commands/server_status.h" #include "mongo/db/db_raii.h" @@ -400,7 +400,8 @@ public: .serverNegotiate(cmdObj, &result); } - SASLMechanismAdvertiser::advertise(opCtx, cmdObj, &result); + auto& saslMechanismRegistry = SASLServerMechanismRegistry::get(opCtx->getServiceContext()); + saslMechanismRegistry.advertiseMechanismNamesForUser(opCtx, cmdObj, &result); return true; } diff --git a/src/mongo/s/commands/cluster_is_master_cmd.cpp b/src/mongo/s/commands/cluster_is_master_cmd.cpp index eaee3701cec..2b45078ab2d 100644 --- a/src/mongo/s/commands/cluster_is_master_cmd.cpp +++ b/src/mongo/s/commands/cluster_is_master_cmd.cpp @@ -28,7 +28,7 @@ #include "mongo/platform/basic.h" -#include "mongo/db/auth/sasl_mechanism_advertiser.h" +#include "mongo/db/auth/sasl_mechanism_registry.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" #include "mongo/db/logical_session_id.h" @@ -133,7 +133,8 @@ public: MessageCompressorManager::forSession(opCtx->getClient()->session()) .serverNegotiate(cmdObj, &result); - SASLMechanismAdvertiser::advertise(opCtx, cmdObj, &result); + auto& saslMechanismRegistry = SASLServerMechanismRegistry::get(opCtx->getServiceContext()); + saslMechanismRegistry.advertiseMechanismNamesForUser(opCtx, cmdObj, &result); return true; } |