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:
parent
4ef0fe789e
commit
25d521ca32
@ -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";
|
||||
|
21
jstests/auth/system_user_exception.js
Normal file
21
jstests/auth/system_user_exception.js
Normal 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);
|
||||
|
||||
})();
|
@ -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',
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
@ -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
|
@ -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
|
@ -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
|
||||
|
@ -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");
|
||||
|
@ -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
|
@ -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
|
@ -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
|
179
src/mongo/db/auth/sasl_mechanism_registry.cpp
Normal file
179
src/mongo/db/auth/sasl_mechanism_registry.cpp
Normal 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
|
336
src/mongo/db/auth/sasl_mechanism_registry.h
Normal file
336
src/mongo/db/auth/sasl_mechanism_registry.h
Normal 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
|
350
src/mongo/db/auth/sasl_mechanism_registry_test.cpp
Normal file
350
src/mongo/db/auth/sasl_mechanism_registry_test.cpp
Normal 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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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
|
@ -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
|
@ -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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user