diff --git a/jstests/auth/sasl_mechanism_discovery.js b/jstests/auth/sasl_mechanism_discovery.js new file mode 100644 index 0000000000000..c2dfb7aabcc1c --- /dev/null +++ b/jstests/auth/sasl_mechanism_discovery.js @@ -0,0 +1,36 @@ +// Tests that a client may discover a user's supported SASL mechanisms via isMaster. +(function() { + "use strict"; + + function runTest(conn) { + var db = conn.getDB("admin"); + var externalDB = conn.getDB("$external"); + + // Make users + assert.commandWorked(db.runCommand({createUser: "user", pwd: "pwd", roles: []})); + assert.commandWorked(externalDB.runCommand({createUser: "user", roles: []})); + + // Internal users should support SCRAM-SHA-1. + var isMasterResult = + assert.commandWorked(db.runCommand({isMaster: 1, saslSupportedMechs: "admin.user"})); + assert.eq(["SCRAM-SHA-1"], isMasterResult.saslSupportedMechs, tojson(isMasterResult)); + + // External users should support PLAIN, but not SCRAM-SHA-1. + isMasterResult = assert.commandWorked( + db.runCommand({isMaster: 1, saslSupportedMechs: "$external.user"})); + assert.eq(["PLAIN"], isMasterResult.saslSupportedMechs, tojson(isMasterResult)); + } + + // Test standalone. + var m = MongoRunner.runMongod({setParameter: "authenticationMechanisms=SCRAM-SHA-1,PLAIN"}); + runTest(m); + MongoRunner.stopMongod(m); + + // Test mongos. + var st = new ShardingTest({ + shards: 0, + other: {mongosOptions: {setParameter: "authenticationMechanisms=PLAIN,SCRAM-SHA-1"}} + }); + runTest(st.s0); + st.stop(); +})(); diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index e1bfbbabeac57..9be76b3c20dcc 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -61,6 +61,7 @@ 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', diff --git a/src/mongo/db/auth/sasl_mechanism_advertiser.cpp b/src/mongo/db/auth/sasl_mechanism_advertiser.cpp new file mode 100644 index 0000000000000..75f6aa191edc2 --- /dev/null +++ b/src/mongo/db/auth/sasl_mechanism_advertiser.cpp @@ -0,0 +1,80 @@ +/** + * 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 . + * + * 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" + +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; + } +} +} // namespace + + +void SASLMechanismAdvertiser::advertise(OperationContext* opCtx, + const BSONObj& cmdObj, + BSONObjBuilder* result) { + BSONElement saslSupportedMechs = cmdObj["saslSupportedMechs"]; + if (saslSupportedMechs.type() == BSONType::String) { + AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext()); + + UserName userName = uassertStatusOK(UserName::parse(saslSupportedMechs.String())); + + User* userObj; + Status status = authManager->acquireUser(opCtx, userName, &userObj); + uassertStatusOK(status); + auto credentials = userObj->getCredentials(); + authManager->releaseUser(userObj); + + BSONArrayBuilder mechanismsBuilder; + if (credentials.isExternal) { + for (const StringData& userMechanism : {"GSSAPI", "PLAIN"}) { + appendMechanismIfSupported(userMechanism, &mechanismsBuilder); + } + } else if (credentials.scram().isValid()) { + appendMechanismIfSupported("SCRAM-SHA-1", &mechanismsBuilder); + } + + result->appendArray("saslSupportedMechs", mechanismsBuilder.arr()); + } +} + + +} // namespace mongo diff --git a/src/mongo/db/auth/sasl_mechanism_advertiser.h b/src/mongo/db/auth/sasl_mechanism_advertiser.h new file mode 100644 index 0000000000000..cae0e65895deb --- /dev/null +++ b/src/mongo/db/auth/sasl_mechanism_advertiser.h @@ -0,0 +1,42 @@ +/** + * 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 . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the GNU Affero General Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ + +#pragma once + +#include "mongo/bson/bsonobj.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/db/operation_context.h" + +namespace mongo { + +class SASLMechanismAdvertiser { +public: + static void advertise(OperationContext* opCtx, const BSONObj& cmdObj, BSONObjBuilder* result); +}; + +} // namespace mongo diff --git a/src/mongo/db/auth/user_name.cpp b/src/mongo/db/auth/user_name.cpp index 7b56a87dbd744..3bcdf3ad813f8 100644 --- a/src/mongo/db/auth/user_name.cpp +++ b/src/mongo/db/auth/user_name.cpp @@ -47,6 +47,21 @@ UserName::UserName(StringData user, StringData dbname) { _splitPoint = user.size(); } + +StatusWith UserName::parse(StringData userNameStr) { + size_t splitPoint = userNameStr.find('.'); + + if (splitPoint == std::string::npos) { + return Status(ErrorCodes::BadValue, + "username must contain a '.' separated database.user pair"); + } + + StringData userDBPortion = userNameStr.substr(0, splitPoint); + StringData userNamePortion = userNameStr.substr(splitPoint + 1); + + return UserName(userNamePortion, userDBPortion); +} + std::ostream& operator<<(std::ostream& os, const UserName& name) { return os << name.getFullName(); } diff --git a/src/mongo/db/auth/user_name.h b/src/mongo/db/auth/user_name.h index 97a09270115fd..260604d6a18db 100644 --- a/src/mongo/db/auth/user_name.h +++ b/src/mongo/db/auth/user_name.h @@ -33,6 +33,7 @@ #include "mongo/base/disallow_copying.h" +#include "mongo/base/status_with.h" #include "mongo/base/string_data.h" namespace mongo { @@ -47,6 +48,11 @@ class UserName { UserName() : _splitPoint(0) {} UserName(StringData user, StringData dbname); + /** + * Parses a string of the form "db.username" into a UserName object. + */ + static StatusWith parse(StringData userNameStr); + /** * Gets the user part of a UserName. */ diff --git a/src/mongo/db/repl/replication_info.cpp b/src/mongo/db/repl/replication_info.cpp index 0911a180d36db..d69d6b362885e 100644 --- a/src/mongo/db/repl/replication_info.cpp +++ b/src/mongo/db/repl/replication_info.cpp @@ -33,6 +33,7 @@ #include #include "mongo/client/connpool.h" +#include "mongo/db/auth/sasl_mechanism_advertiser.h" #include "mongo/db/client.h" #include "mongo/db/commands/server_status.h" #include "mongo/db/db_raii.h" @@ -389,6 +390,8 @@ class CmdIsMaster : public BasicCommand { .serverNegotiate(cmdObj, &result); } + SASLMechanismAdvertiser::advertise(opCtx, cmdObj, &result); + return true; } } cmdismaster; diff --git a/src/mongo/s/commands/cluster_is_master_cmd.cpp b/src/mongo/s/commands/cluster_is_master_cmd.cpp index 441648bf7e9cc..531f758859fbd 100644 --- a/src/mongo/s/commands/cluster_is_master_cmd.cpp +++ b/src/mongo/s/commands/cluster_is_master_cmd.cpp @@ -28,6 +28,7 @@ #include "mongo/platform/basic.h" +#include "mongo/db/auth/sasl_mechanism_advertiser.h" #include "mongo/db/client.h" #include "mongo/db/commands.h" #include "mongo/db/logical_session_id.h" @@ -132,6 +133,8 @@ class CmdIsMaster : public BasicCommand { MessageCompressorManager::forSession(opCtx->getClient()->session()) .serverNegotiate(cmdObj, &result); + SASLMechanismAdvertiser::advertise(opCtx, cmdObj, &result); + return true; }