0
0
mirror of https://github.com/mongodb/mongo.git synced 2024-12-01 09:32:32 +01:00

SERVER-32966: Add SASL server mechanism registry

This commit is contained in:
Spencer Jackson 2018-01-12 19:11:14 -05:00
parent 4ef0fe789e
commit 25d521ca32
25 changed files with 1354 additions and 1179 deletions

View File

@ -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";

View File

@ -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);
})();

View File

@ -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',
],
)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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())
return status;
std::string responsePayload;
// Passing in a payload and extracting a responsePayload
status = session->step(payload, &responsePayload);
if (!status.isOK()) {
log() << session->getMechanism() << " authentication failed for "
<< session->getPrincipalId() << " on " << session->getAuthenticationDatabase()
<< " from client " << client->getRemote().toString() << " ; " << redact(status);
return status;
}
auto& mechanism = session->getMechanism();
// Passing in a payload and extracting a responsePayload
StatusWith<std::string> swResponse = mechanism.step(opCtx, payload);
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);
if (session->isDone()) {
audit::logAuthentication(client,
session->getMechanism(),
UserName(session->getPrincipalId(), db),
status.code());
} else {
AuthenticationSession::swap(client, sessionGuard);
StatusWith<std::unique_ptr<AuthenticationSession>> swSession =
doSaslStart(opCtx, db, cmdObj, &result);
CommandHelpers::appendCommandStatus(result, swSession.getStatus());
if (!swSession.isOK()) {
return false;
}
return status.isOK();
auto session = std::move(swSession.getValue());
auto& mechanism = session->getMechanism();
if (mechanism.isDone()) {
audit::logAuthentication(client,
mechanismName,
UserName(mechanism.getPrincipalName(), db),
swSession.getStatus().code());
} else {
AuthenticationSession::swap(client, session);
}
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");

View File

@ -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

View File

@ -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

View File

@ -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();
virtual Status start(StringData authenticationDatabase,
StringData mechanism,
StringData serviceName,
StringData serviceHostname,
int64_t conversationId,
bool autoAuthorize);
virtual Status step(StringData inputData, std::string* outputData);
virtual std::string getPrincipalId() const;
virtual const char* getMechanism() const;
private:
std::string _mechanism;
std::unique_ptr<SaslServerConversation> _saslConversation;
struct PLAINPolicy {
static constexpr StringData getName() {
return "PLAIN"_sd;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{};
}
};
struct SCRAMSHA1Policy {
using HashBlock = SHA1Block;
static constexpr StringData getName() {
return "SCRAM-SHA-1"_sd;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
}
};
struct SCRAMSHA256Policy {
using HashBlock = SHA256Block;
static constexpr StringData getName() {
return "SCRAM-SHA-256"_sd;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
}
};
struct GSSAPIPolicy {
static constexpr StringData getName() {
return "GSSAPI"_sd;
}
static SecurityPropertySet getProperties() {
return SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth};
}
};
} // namespace mongo

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
virtual ~SaslPLAINServerConversation();
explicit SASLPlainServerMechanism(std::string authenticationDatabase)
: MakeServerMechanism<PLAINPolicy>(std::move(authenticationDatabase)) {}
virtual StatusWith<bool> step(StringData inputData, std::string* outputData);
private:
StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx,
StringData input) final;
};
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

View File

@ -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

View File

@ -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,94 +56,10 @@ public:
* authentication conversation is finished or not.
*
**/
StatusWith<bool> step(StringData inputData, std::string* outputData) override;
StatusWith<std::tuple<bool, std::string>> stepImpl(OperationContext* opCtx,
StringData inputData);
/**
* 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;
/**
* 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;
private:
/**
* Parse client-first-message and generate server-first-message
**/
StatusWith<bool> _firstStep(StringData input, std::string* outputData);
/**
* Parse client-final-message and generate server-final-message
**/
StatusWith<bool> _secondStep(StringData input, std::string* outputData);
protected:
int _step{0};
std::string _authMessage;
User::CredentialData _creds;
// client and server nonce concatenated
std::string _nonce;
};
template <typename HashBlock>
class SaslSCRAMServerConversationImpl : public SaslSCRAMServerConversation {
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);
}
StatusWith<std::string> saslPrep(StringData str) const final {
StatusWith<std::string> saslPrep(StringData str) const {
if (std::is_same<SHA1Block, HashBlock>::value) {
return str.toString();
} else {
@ -154,9 +68,39 @@ public:
}
private:
scram::Secrets<HashBlock> _credentials;
/**
* Parse client-first-message and generate server-first-message
**/
StatusWith<std::tuple<bool, std::string>> _firstStep(OperationContext* opCtx, StringData input);
/**
* Parse client-final-message and generate server-final-message
**/
StatusWith<std::tuple<bool, std::string>> _secondStep(OperationContext* opCtx,
StringData input);
int _step{0};
std::string _authMessage;
User::SCRAMCredentials<HashBlock> _scramCredentials;
scram::Secrets<HashBlock> _secrets;
// client and server nonce concatenated
std::string _nonce;
};
using SaslSCRAMSHA1ServerConversation = SaslSCRAMServerConversationImpl<SHA1Block>;
template <typename ScramMechanism>
class SCRAMServerFactory : public MakeServerFactory<ScramMechanism> {
public:
bool canMakeMechanismForUser(const User* user) const final {
auto credentials = user->getCredentials();
return credentials.scram<typename ScramMechanism::HashBlock>().isValid();
}
};
using SaslSCRAMSHA1ServerMechanism = SaslSCRAMServerMechanism<SCRAMSHA1Policy>;
using SCRAMSHA1ServerFactory = SCRAMServerFactory<SaslSCRAMSHA1ServerMechanism>;
using SaslSCRAMSHA256ServerMechanism = SaslSCRAMServerMechanism<SCRAMSHA256Policy>;
using SCRAMSHA256ServerFactory = SCRAMServerFactory<SaslSCRAMSHA256ServerMechanism>;
} // namespace mongo

View File

@ -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 {
saslClientSession.reset();
saslServerSession.reset();
authzSession.reset();
authzManager.reset();
authzManagerExternalState = nullptr;
opCtx.reset();
client.reset();
serviceContext.reset();
saslClientSession.reset();
saslServerSession.reset();
authzSession.reset();
authzManagerExternalState = nullptr;
}
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>

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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;
}

View File

@ -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;
}