diff options
Diffstat (limited to 'src/mongo/client/authenticate.cpp')
-rw-r--r-- | src/mongo/client/authenticate.cpp | 232 |
1 files changed, 152 insertions, 80 deletions
diff --git a/src/mongo/client/authenticate.cpp b/src/mongo/client/authenticate.cpp index e32164a9228..6dc0337d627 100644 --- a/src/mongo/client/authenticate.cpp +++ b/src/mongo/client/authenticate.cpp @@ -40,9 +40,12 @@ #include "mongo/bson/util/bson_extract.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/config.h" +#include "mongo/db/auth/authorization_manager.h" #include "mongo/db/auth/sasl_command_constants.h" #include "mongo/db/server_options.h" #include "mongo/rpc/get_status_from_command_result.h" +#include "mongo/rpc/op_msg_rpc_impls.h" +#include "mongo/stdx/mutex.h" #include "mongo/util/log.h" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/net/ssl_options.h" @@ -56,21 +59,11 @@ using executor::RemoteCommandResponse; using AuthRequest = StatusWith<RemoteCommandRequest>; -const char* const kMechanismMongoCR = "MONGODB-CR"; -const char* const kMechanismMongoX509 = "MONGODB-X509"; -const char* const kMechanismSaslPlain = "PLAIN"; -const char* const kMechanismGSSAPI = "GSSAPI"; -const char* const kMechanismScramSha1 = "SCRAM-SHA-1"; - namespace { const char* const kUserSourceFieldName = "userSource"; const BSONObj kGetNonceCmd = BSON("getnonce" << 1); -bool isOk(const BSONObj& o) { - return getStatusFromCommandResult(o).isOK(); -} - StatusWith<std::string> extractDBField(const BSONObj& params) { std::string db; if (params.hasField(kUserSourceFieldName)) { @@ -90,15 +83,16 @@ StatusWith<std::string> extractDBField(const BSONObj& params) { // MONGODB-CR // -void authMongoCRImpl(RunCommandHook cmd, const BSONObj& params, AuthCompletionHandler handler) { - handler({ErrorCodes::AuthenticationFailed, "MONGODB-CR support was removed in MongoDB 3.8"}); +Future<void> authMongoCRImpl(RunCommandHook cmd, const BSONObj& params) { + return Status(ErrorCodes::AuthenticationFailed, + "MONGODB-CR support was removed in MongoDB 4.0"); } // // X-509 // -AuthRequest createX509AuthCmd(const BSONObj& params, StringData clientName) { +StatusWith<OpMsgRequest> createX509AuthCmd(const BSONObj& params, StringData clientName) { if (clientName.empty()) { return {ErrorCodes::AuthenticationFailed, "Please enable SSL on the client-side to use the MONGODB-X509 authentication " @@ -108,9 +102,6 @@ AuthRequest createX509AuthCmd(const BSONObj& params, StringData clientName) { if (!db.isOK()) return std::move(db.getStatus()); - auto request = RemoteCommandRequest(); - request.dbname = db.getValue(); - std::string username; auto response = bsonExtractStringFieldWithDefault( params, saslCommandUserFieldName, clientName.toString(), &username); @@ -126,115 +117,196 @@ AuthRequest createX509AuthCmd(const BSONObj& params, StringData clientName) { return {ErrorCodes::AuthenticationFailed, message.str()}; } - request.cmdObj = BSON("authenticate" << 1 << "mechanism" - << "MONGODB-X509" - << "user" - << username); - return std::move(request); + return OpMsgRequest::fromDBAndBody(db.getValue(), + BSON("authenticate" << 1 << "mechanism" + << "MONGODB-X509" + << "user" + << username)); } // Use the MONGODB-X509 protocol to authenticate as "username." The certificate details // have already been communicated automatically as part of the connect call. -void authX509(RunCommandHook runCommand, - const BSONObj& params, - StringData clientName, - AuthCompletionHandler handler) { +Future<void> authX509(RunCommandHook runCommand, const BSONObj& params, StringData clientName) { invariant(runCommand); - invariant(handler); // Just 1 step: send authenticate command, receive response auto authRequest = createX509AuthCmd(params, clientName); if (!authRequest.isOK()) - return handler(std::move(authRequest.getStatus())); + return authRequest.getStatus(); - runCommand(authRequest.getValue(), handler); + // The runCommand hook checks whether the command returned { ok: 1.0 }, and we don't need to + // extract anything from the command payload, so this is just turning a Future<BSONObj> + // into a Future<void> + return runCommand(authRequest.getValue()).then([](BSONObj obj) { return Status::OK(); }); } +} // namespace // // General Auth // -bool isFailedAuthOk(const AuthResponse& response) { - return (response.status == ErrorCodes::AuthenticationFailed && - serverGlobalParams.transitionToAuth); -} - -void auth(RunCommandHook runCommand, - const BSONObj& params, - const HostAndPort& hostname, - StringData clientName, - AuthCompletionHandler handler) { +Future<void> authenticateClient(const BSONObj& params, + const HostAndPort& hostname, + const std::string& clientName, + RunCommandHook runCommand) { std::string mechanism; - auto authCompletionHandler = [handler](AuthResponse response) { - if (isFailedAuthOk(response)) { + auto errorHandler = [](Status status) { + if (serverGlobalParams.transitionToAuth && !status.isA<ErrorCategory::NetworkError>()) { // If auth failed in transitionToAuth, just pretend it succeeded. log() << "Failed to authenticate in transitionToAuth, falling back to no " "authentication."; - // We need to mock a successful AuthResponse. - return handler(AuthResponse(RemoteCommandResponse(BSON("ok" << 1), Milliseconds(0)))); + return Status::OK(); } - // otherwise, call handler - return handler(std::move(response)); + return status; }; auto response = bsonExtractStringField(params, saslCommandMechanismFieldName, &mechanism); if (!response.isOK()) - return handler(std::move(response)); + return response; if (params.hasField(saslCommandUserDBFieldName) && params.hasField(kUserSourceFieldName)) { - return handler({ErrorCodes::AuthenticationFailed, - "You cannot specify both 'db' and 'userSource'. Please use only 'db'."}); + return Status(ErrorCodes::AuthenticationFailed, + "You cannot specify both 'db' and 'userSource'. Please use only 'db'."); } if (mechanism == kMechanismMongoCR) - return authMongoCR(runCommand, params, authCompletionHandler); + return authMongoCR(runCommand, params).onError(errorHandler); #ifdef MONGO_CONFIG_SSL else if (mechanism == kMechanismMongoX509) - return authX509(runCommand, params, clientName, authCompletionHandler); + return authX509(runCommand, params, clientName).onError(errorHandler); #endif else if (saslClientAuthenticate != nullptr) - return saslClientAuthenticate(runCommand, hostname, params, authCompletionHandler); + return saslClientAuthenticate(runCommand, hostname, params).onError(errorHandler); - return handler({ErrorCodes::AuthenticationFailed, - mechanism + " mechanism support not compiled into client library."}); + return Status(ErrorCodes::AuthenticationFailed, + mechanism + " mechanism support not compiled into client library."); }; -void asyncAuth(RunCommandHook runCommand, - const BSONObj& params, - const HostAndPort& hostname, - StringData clientName, - AuthCompletionHandler handler) { - auth(runCommand, params, hostname, clientName, std::move(handler)); +AuthMongoCRHandler authMongoCR = authMongoCRImpl; + +static stdx::mutex internalAuthKeysMutex; +static bool internalAuthSet = false; +static std::vector<std::string> internalAuthKeys; +static BSONObj internalAuthParams; + +void setInternalAuthKeys(const std::vector<std::string>& keys) { + stdx::lock_guard<stdx::mutex> lk(internalAuthKeysMutex); + + internalAuthKeys = keys; + fassert(50996, internalAuthKeys.size() > 0); + internalAuthSet = true; } -} // namespace +void setInternalUserAuthParams(BSONObj obj) { + stdx::lock_guard<stdx::mutex> lk(internalAuthKeysMutex); + internalAuthParams = obj.getOwned(); + internalAuthKeys.clear(); + internalAuthSet = true; +} -AuthMongoCRHandler authMongoCR = authMongoCRImpl; +bool hasMultipleInternalAuthKeys() { + stdx::lock_guard<stdx::mutex> lk(internalAuthKeysMutex); + return internalAuthSet && internalAuthKeys.size() > 1; +} -void authenticateClient(const BSONObj& params, - const HostAndPort& hostname, - StringData clientName, - RunCommandHook runCommand, - AuthCompletionHandler handler) { - if (handler) { - // Run asynchronously - return asyncAuth(std::move(runCommand), params, hostname, clientName, std::move(handler)); - } else { - // Run synchronously through async framework - // NOTE: this assumes that runCommand executes synchronously. - asyncAuth(runCommand, params, hostname, clientName, [](AuthResponse response) { - // DBClient expects us to throw in case of an auth error. - uassertStatusOK(response.status); - - auto serverResponse = response.data; - uassert(ErrorCodes::AuthenticationFailed, - serverResponse["errmsg"].str(), - isOk(serverResponse)); - }); +bool isInternalAuthSet() { + stdx::lock_guard<stdx::mutex> lk(internalAuthKeysMutex); + return internalAuthSet; +} + +BSONObj getInternalAuthParams(size_t idx, const std::string& mechanism) { + stdx::lock_guard<stdx::mutex> lk(internalAuthKeysMutex); + if (!internalAuthSet) { + return BSONObj(); + } + + // If we've set a specific BSONObj as the internal auth pararms, return it if the index + // is zero (there are no alternate credentials if we've set a BSONObj explicitly). + if (!internalAuthParams.isEmpty()) { + return idx == 0 ? internalAuthParams : BSONObj(); + } + + // If the index is larger than the number of keys we know about then return an empty + // BSONObj. + if (idx + 1 > internalAuthKeys.size()) { + return BSONObj(); } + + auto password = internalAuthKeys.at(idx); + if (mechanism == kMechanismScramSha1) { + password = mongo::createPasswordDigest( + internalSecurity.user->getName().getUser().toString(), password); + } + + return BSON(saslCommandMechanismFieldName << mechanism << saslCommandUserDBFieldName + << internalSecurity.user->getName().getDB() + << saslCommandUserFieldName + << internalSecurity.user->getName().getUser() + << saslCommandPasswordFieldName + << password + << saslCommandDigestPasswordFieldName + << false); +} + +Future<std::string> negotiateSaslMechanism(RunCommandHook runCommand, + const UserName& username, + boost::optional<std::string> mechanismHint) { + if (mechanismHint && !mechanismHint->empty()) { + return Future<std::string>::makeReady(*mechanismHint); + } + + const auto request = + BSON("ismaster" << 1 << "saslSupportedMechs" << username.getUnambiguousName()); + return runCommand(OpMsgRequest::fromDBAndBody("admin"_sd, std::move(request))) + .then([](BSONObj reply) -> Future<std::string> { + auto mechsArrayObj = reply.getField("saslSupportedMechs"); + if (mechsArrayObj.type() != Array) { + return Status{ErrorCodes::BadValue, "Expected array of SASL mechanism names"}; + } + + auto obj = mechsArrayObj.Obj(); + std::vector<std::string> availableMechanisms; + for (const auto elem : obj) { + if (elem.type() != String) { + return Status{ErrorCodes::BadValue, "Expected array of SASL mechanism names"}; + } + availableMechanisms.push_back(elem.checkAndGetStringData().toString()); + // The drivers spec says that if SHA-256 is available then it MUST be selected + // as the SASL mech. + if (availableMechanisms.back() == kMechanismScramSha256) { + return availableMechanisms.back(); + } + } + + return availableMechanisms.empty() ? kInternalAuthFallbackMechanism.toString() + : availableMechanisms.front(); + }); +} + +Future<void> authenticateInternalClient(const std::string& clientSubjectName, + boost::optional<std::string> mechanismHint, + RunCommandHook runCommand) { + return negotiateSaslMechanism(runCommand, internalSecurity.user->getName(), mechanismHint) + .then([runCommand, clientSubjectName](std::string mechanism) -> Future<void> { + auto params = getInternalAuthParams(0, mechanism); + if (params.isEmpty()) { + return Status(ErrorCodes::BadValue, + "Missing authentication parameters for internal user auth"); + } + return authenticateClient(params, HostAndPort(), clientSubjectName, runCommand) + .onError<ErrorCodes::AuthenticationFailed>( + [runCommand, clientSubjectName, mechanism](Status status) -> Future<void> { + auto altCreds = getInternalAuthParams(1, mechanism); + if (!altCreds.isEmpty()) { + return authenticateClient( + altCreds, HostAndPort(), clientSubjectName, runCommand); + } + return status; + }); + }); } BSONObj buildAuthParams(StringData dbname, |