/* Copyright 2015 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. */ #define MONGO_LOG_DEFAULT_COMPONENT ::mongo::logger::LogComponent::kAccessControl #include "mongo/platform/basic.h" #include "mongo/client/authenticate.h" #include "mongo/base/status.h" #include "mongo/base/status_with.h" #include "mongo/bson/json.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/config.h" #include "mongo/db/server_options.h" #include "mongo/rpc/get_status_from_command_result.h" #include "mongo/util/log.h" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/net/ssl_options.h" #include "mongo/util/password_digest.h" namespace mongo { namespace auth { using executor::RemoteCommandRequest; using executor::RemoteCommandResponse; using AuthRequest = StatusWith; 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 extractDBField(const BSONObj& params) { std::string db; if (params.hasField(kUserSourceFieldName)) { if (!bsonExtractStringField(params, kUserSourceFieldName, &db).isOK()) { return {ErrorCodes::AuthenticationFailed, "userSource field must contain a string"}; } } else { if (!bsonExtractStringField(params, saslCommandUserDBFieldName, &db).isOK()) { return {ErrorCodes::AuthenticationFailed, "db field must contain a string"}; } } return std::move(db); } // // MONGODB-CR // AuthRequest createMongoCRGetNonceCmd(const BSONObj& params) { auto db = extractDBField(params); if (!db.isOK()) return std::move(db.getStatus()); auto request = RemoteCommandRequest(); request.cmdObj = kGetNonceCmd; request.dbname = db.getValue(); return std::move(request); } AuthRequest createMongoCRAuthenticateCmd(const BSONObj& params, StringData nonce) { std::string username; auto response = bsonExtractStringField(params, saslCommandUserFieldName, &username); if (!response.isOK()) return response; std::string password; response = bsonExtractStringField(params, saslCommandPasswordFieldName, &password); if (!response.isOK()) return response; bool shouldDigest; response = bsonExtractBooleanFieldWithDefault( params, saslCommandDigestPasswordFieldName, true, &shouldDigest); if (!response.isOK()) return response; std::string digested = password; if (shouldDigest) digested = createPasswordDigest(username, password); auto db = extractDBField(params); if (!db.isOK()) return std::move(db.getStatus()); auto request = RemoteCommandRequest(); request.dbname = db.getValue(); BSONObjBuilder b; { b << "authenticate" << 1 << "nonce" << nonce << "user" << username; md5digest d; { md5_state_t st; md5_init(&st); md5_append(&st, reinterpret_cast(nonce.rawData()), nonce.size()); md5_append(&st, reinterpret_cast(username.c_str()), username.size()); md5_append(&st, reinterpret_cast(digested.c_str()), digested.size()); md5_finish(&st, d); } b << "key" << digestToString(d); request.cmdObj = b.obj(); } return std::move(request); } void authMongoCR(RunCommandHook runCommand, const BSONObj& params, AuthCompletionHandler handler) { invariant(runCommand); invariant(handler); // Step 1: send getnonce command, receive nonce auto nonceRequest = createMongoCRGetNonceCmd(params); if (!nonceRequest.isOK()) return handler(std::move(nonceRequest.getStatus())); runCommand(nonceRequest.getValue(), [runCommand, params, handler](AuthResponse response) { if (!response.isOK()) return handler(std::move(response)); // Ensure response was valid std::string nonce; BSONObj nonceResponse = response.data; auto valid = bsonExtractStringField(nonceResponse, "nonce", &nonce); if (!valid.isOK()) return handler({ErrorCodes::AuthenticationFailed, "Invalid nonce response: " + nonceResponse.toString()}); // Step 2: send authenticate command, receive response auto authRequest = createMongoCRAuthenticateCmd(params, nonce); if (!authRequest.isOK()) return handler(std::move(authRequest.getStatus())); runCommand(authRequest.getValue(), handler); }); } // // X-509 // AuthRequest 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 " "mechanism."}; } auto db = extractDBField(params); 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); if (!response.isOK()) { return response; } if (username != clientName.toString()) { StringBuilder message; message << "Username \""; message << params[saslCommandUserFieldName].valuestr(); message << "\" does not match the provided client certificate user \""; message << clientName.toString() << "\""; return {ErrorCodes::AuthenticationFailed, message.str()}; } request.cmdObj = BSON("authenticate" << 1 << "mechanism" << "MONGODB-X509" << "user" << username); return std::move(request); } // 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) { 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())); runCommand(authRequest.getValue(), handler); } // // 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) { std::string mechanism; auto authCompletionHandler = [handler](AuthResponse response) { if (isFailedAuthOk(response)) { // 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), BSONObj(), Milliseconds(0)))); } // otherwise, call handler return handler(std::move(response)); }; auto response = bsonExtractStringField(params, saslCommandMechanismFieldName, &mechanism); if (!response.isOK()) return handler(std::move(response)); if (params.hasField(saslCommandUserDBFieldName) && params.hasField(kUserSourceFieldName)) { return handler({ErrorCodes::AuthenticationFailed, "You cannot specify both 'db' and 'userSource'. Please use only 'db'."}); } if (mechanism == kMechanismMongoCR) return authMongoCR(runCommand, params, authCompletionHandler); #ifdef MONGO_CONFIG_SSL else if (mechanism == kMechanismMongoX509) return authX509(runCommand, params, clientName, authCompletionHandler); #endif else if (saslClientAuthenticate != nullptr) return saslClientAuthenticate(runCommand, hostname, params, authCompletionHandler); return handler({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)); } } // namespace 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)); }); } } BSONObj buildAuthParams(StringData dbname, StringData username, StringData passwordText, bool digestPassword) { return BSON(saslCommandMechanismFieldName << "SCRAM-SHA-1" << saslCommandUserDBFieldName << dbname << saslCommandUserFieldName << username << saslCommandPasswordFieldName << passwordText << saslCommandDigestPasswordFieldName << digestPassword); } StringData getSaslCommandUserDBFieldName() { return saslCommandUserDBFieldName; } StringData getSaslCommandUserFieldName() { return saslCommandUserFieldName; } } // namespace auth } // namespace mongo