/** * Copyright (C) 2010 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 . * * 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/db/commands/authentication_commands.h" #include #include #include #include #include "mongo/base/status.h" #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/document.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/db/audit.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.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/mongo_authentication_session.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/security_key.h" #include "mongo/db/client_basic.h" #include "mongo/db/commands.h" #include "mongo/db/jsobj.h" #include "mongo/db/server_options.h" #include "mongo/platform/random.h" #include "mongo/util/concurrency/mutex.h" #include "mongo/util/log.h" #include "mongo/util/md5.hpp" #include "mongo/util/net/ssl_manager.h" #include "mongo/util/text.h" namespace mongo { static bool _isCRAuthDisabled; static bool _isX509AuthDisabled; static const char _nonceAuthenticationDisabledMessage[] = "Challenge-response authentication using getnonce and authenticate commands is disabled."; static const char _x509AuthenticationDisabledMessage[] = "x.509 authentication is disabled."; void CmdAuthenticate::disableAuthMechanism(std::string authMechanism) { if (authMechanism == "MONGODB-CR") { _isCRAuthDisabled = true; } if (authMechanism == "MONGODB-X509") { _isX509AuthDisabled = true; } } /* authentication system.users contains { user : , pwd : , ... } getnonce sends nonce to client client then sends { authenticate:1, nonce64:, user:, key: } where is md5() as a string */ class CmdGetNonce : public Command { public: CmdGetNonce() : Command("getnonce"), _randMutex("getnonce"), _random(SecureRandom::create()) { } virtual bool slaveOk() const { return true; } void help(stringstream& h) const { h << "internal"; } virtual bool isWriteCommandForConfigServer() const { return false; } virtual void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, std::vector* out) {} // No auth required bool run(OperationContext* txn, const string&, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { nonce64 n = getNextNonce(); stringstream ss; ss << hex << n; result.append("nonce", ss.str() ); ClientBasic::getCurrent()->resetAuthenticationSession( new MongoAuthenticationSession(n)); return true; } private: nonce64 getNextNonce() { SimpleMutex::scoped_lock lk(_randMutex); return _random->nextInt64(); } SimpleMutex _randMutex; // Synchronizes accesses to _random. boost::scoped_ptr _random; } cmdGetNonce; void CmdAuthenticate::redactForLogging(mutablebson::Document* cmdObj) { namespace mmb = mutablebson; static const int numRedactedFields = 2; static const char* redactedFields[numRedactedFields] = { "key", "nonce" }; for (int i = 0; i < numRedactedFields; ++i) { for (mmb::Element element = mmb::findFirstChildNamed(cmdObj->root(), redactedFields[i]); element.ok(); element = mmb::findElementNamed(element.rightSibling(), redactedFields[i])) { element.setValueString("xxx"); } } } bool CmdAuthenticate::run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int, string& errmsg, BSONObjBuilder& result, bool fromRepl) { if (!serverGlobalParams.quiet) { mutablebson::Document cmdToLog(cmdObj, mutablebson::Document::kInPlaceDisabled); redactForLogging(&cmdToLog); log() << " authenticate db: " << dbname << " " << cmdToLog; } UserName user(cmdObj.getStringField("user"), dbname); if (Command::testCommandsEnabled && user.getDB() == "admin" && user.getUser() == 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). user = internalSecurity.user->getName(); } std::string mechanism = cmdObj.getStringField("mechanism"); if (mechanism.empty()) { mechanism = "MONGODB-CR"; } Status status = _authenticate(txn, mechanism, user, cmdObj); audit::logAuthentication(ClientBasic::getCurrent(), mechanism, user, status.code()); if (!status.isOK()) { if (!serverGlobalParams.quiet) { log() << "Failed to authenticate " << user << " with mechanism " << mechanism << ": " << status; } if (status.code() == ErrorCodes::AuthenticationFailed) { // Statuses with code AuthenticationFailed may contain messages we do not wish to // reveal to the user, so we return a status with the message "auth failed". appendCommandStatus(result, Status(ErrorCodes::AuthenticationFailed, "auth failed")); } else { appendCommandStatus(result, status); } return false; } result.append("dbname", user.getDB()); result.append("user", user.getUser()); return true; } Status CmdAuthenticate::_authenticate(OperationContext* txn, const std::string& mechanism, const UserName& user, const BSONObj& cmdObj) { if (mechanism == "MONGODB-CR") { return _authenticateCR(txn, user, cmdObj); } #ifdef MONGO_SSL if (mechanism == "MONGODB-X509") { return _authenticateX509(txn, user, cmdObj); } #endif return Status(ErrorCodes::BadValue, "Unsupported mechanism: " + mechanism); } Status CmdAuthenticate::_authenticateCR( OperationContext* txn, const UserName& user, const BSONObj& cmdObj) { if (user == internalSecurity.user->getName() && serverGlobalParams.clusterAuthMode.load() == ServerGlobalParams::ClusterAuthMode_x509) { return Status(ErrorCodes::AuthenticationFailed, "Mechanism x509 is required for internal cluster authentication"); } if (_isCRAuthDisabled) { // SERVER-8461, MONGODB-CR must be enabled for authenticating the internal user, so that // cluster members may communicate with each other. if (user != internalSecurity.user->getName()) { return Status(ErrorCodes::BadValue, _nonceAuthenticationDisabledMessage); } } string key = cmdObj.getStringField("key"); string received_nonce = cmdObj.getStringField("nonce"); if( user.getUser().empty() || key.empty() || received_nonce.empty() ) { sleepmillis(10); return Status(ErrorCodes::ProtocolError, "field missing/wrong type in received authenticate command"); } stringstream digestBuilder; { ClientBasic *client = ClientBasic::getCurrent(); boost::scoped_ptr session; client->swapAuthenticationSession(session); if (!session || session->getType() != AuthenticationSession::SESSION_TYPE_MONGO) { sleepmillis(30); return Status(ErrorCodes::ProtocolError, "No pending nonce"); } else { nonce64 nonce = static_cast(session.get())->getNonce(); digestBuilder << hex << nonce; if (digestBuilder.str() != received_nonce) { sleepmillis(30); return Status(ErrorCodes::AuthenticationFailed, "Received wrong nonce."); } } } User* userObj; Status status = getGlobalAuthorizationManager()->acquireUser(txn, user, &userObj); if (!status.isOK()) { // Failure to find the privilege document indicates no-such-user, a fact that we do not // wish to reveal to the client. So, we return AuthenticationFailed rather than passing // through the returned status. return Status(ErrorCodes::AuthenticationFailed, status.toString()); } string pwd = userObj->getCredentials().password; getGlobalAuthorizationManager()->releaseUser(userObj); md5digest d; { digestBuilder << user.getUser() << pwd; string done = digestBuilder.str(); md5_state_t st; md5_init(&st); md5_append(&st, (const md5_byte_t *) done.c_str(), done.size()); md5_finish(&st, d); } string computed = digestToString( d ); if ( key != computed ) { return Status(ErrorCodes::AuthenticationFailed, "key mismatch"); } AuthorizationSession* authorizationSession = ClientBasic::getCurrent()->getAuthorizationSession(); status = authorizationSession->addAndAuthorizeUser(txn, user); if (!status.isOK()) { return status; } return Status::OK(); } #ifdef MONGO_SSL void canonicalizeClusterDN(std::vector* dn) { // remove all RDNs we don't care about for (size_t i=0; isize(); i++) { std::string& comp = dn->at(i); boost::algorithm::trim(comp); if (!mongoutils::str::startsWith(comp.c_str(), "DC=") && !mongoutils::str::startsWith(comp.c_str(), "O=") && !mongoutils::str::startsWith(comp.c_str(), "OU=")) { dn->erase(dn->begin()+i); i--; } } std::stable_sort(dn->begin(), dn->end()); } bool CmdAuthenticate::_clusterIdMatch(const std::string& subjectName, const std::string& srvSubjectName) { std::vector clientRDN = StringSplitter::split(subjectName, ","); std::vector serverRDN = StringSplitter::split(srvSubjectName, ","); canonicalizeClusterDN(&clientRDN); canonicalizeClusterDN(&serverRDN); if (clientRDN.size() == 0 || clientRDN.size() != serverRDN.size()) { return false; } for (size_t i=0; i < serverRDN.size(); i++) { if(clientRDN[i] != serverRDN[i]) { return false; } } return true; } Status CmdAuthenticate::_authenticateX509( OperationContext* txn, const UserName& user, const BSONObj& cmdObj) { if (!getSSLManager()) { return Status(ErrorCodes::ProtocolError, "SSL support is required for the MONGODB-X509 mechanism."); } if(user.getDB() != "$external") { return Status(ErrorCodes::ProtocolError, "X.509 authentication must always use the $external database."); } ClientBasic *client = ClientBasic::getCurrent(); AuthorizationSession* authorizationSession = client->getAuthorizationSession(); std::string subjectName = client->port()->getX509SubjectName(); if (!getSSLManager()->getSSLConfiguration().hasCA) { return Status(ErrorCodes::AuthenticationFailed, "Unable to verify x.509 certificate, as no CA has been provided."); } else if (user.getUser() != subjectName) { return Status(ErrorCodes::AuthenticationFailed, "There is no x.509 client certificate matching the user."); } else { std::string srvSubjectName = getSSLManager()->getSSLConfiguration().serverSubjectName; // Handle internal cluster member auth, only applies to server-server connections if (_clusterIdMatch(subjectName, srvSubjectName)) { int clusterAuthMode = serverGlobalParams.clusterAuthMode.load(); if (clusterAuthMode == ServerGlobalParams::ClusterAuthMode_undefined || clusterAuthMode == ServerGlobalParams::ClusterAuthMode_keyFile) { return Status(ErrorCodes::AuthenticationFailed, "The provided certificate " "can only be used for cluster authentication, not client " "authentication. The current configuration does not allow " "x.509 cluster authentication, check the --clusterAuthMode flag"); } authorizationSession->grantInternalAuthorization(); } // Handle normal client authentication, only applies to client-server connections else { if (_isX509AuthDisabled) { return Status(ErrorCodes::BadValue, _x509AuthenticationDisabledMessage); } Status status = authorizationSession->addAndAuthorizeUser(txn, user); if (!status.isOK()) { return status; } } return Status::OK(); } } #endif CmdAuthenticate cmdAuthenticate; class CmdLogout : public Command { public: virtual bool slaveOk() const { return true; } virtual void addRequiredPrivileges(const std::string& dbname, const BSONObj& cmdObj, std::vector* out) {} // No auth required void help(stringstream& h) const { h << "de-authenticate"; } virtual bool isWriteCommandForConfigServer() const { return false; } CmdLogout() : Command("logout") {} bool run(OperationContext* txn, const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationSession* authSession = ClientBasic::getCurrent()->getAuthorizationSession(); authSession->logoutDatabase(dbname); if (Command::testCommandsEnabled && dbname == "admin") { // Allows logging out as the internal user against the admin database, however // this actually logs out of the local database as well. 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 logout as the internal user // without this). authSession->logoutDatabase("local"); } return true; } } cmdLogout; }