/* * 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 . * * 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/base/init.h" #include "mongo/base/status.h" #include "mongo/base/string_data.h" #include "mongo/bson/mutable/algorithm.h" #include "mongo/bson/mutable/document.h" #include "mongo/bson/util/bson_extract.h" #include "mongo/client/sasl_client_authenticate.h" #include "mongo/db/audit.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/mongo_authentication_session.h" #include "mongo/db/auth/sasl_authentication_session.h" #include "mongo/db/auth/sasl_options.h" #include "mongo/db/client_basic.h" #include "mongo/db/commands.h" #include "mongo/db/commands/authentication_commands.h" #include "mongo/db/server_options.h" #include "mongo/util/base64.h" #include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/sequence_util.h" #include "mongo/util/stringutils.h" namespace mongo { namespace { using std::stringstream; const bool autoAuthorizeDefault = true; class CmdSaslStart : public Command { public: CmdSaslStart(); virtual ~CmdSaslStart(); virtual void addRequiredPrivileges(const std::string&, const BSONObj&, std::vector*) {} void redactForLogging(mutablebson::Document* cmdObj) override; virtual bool run(OperationContext* txn, const std::string& db, BSONObj& cmdObj, int options, std::string& ignored, BSONObjBuilder& result); virtual void help(stringstream& help) const; virtual bool supportsWriteConcern(const BSONObj& cmd) const override { return false; } virtual bool slaveOk() const { return true; } virtual bool requiresAuth() { return false; } }; class CmdSaslContinue : public Command { public: CmdSaslContinue(); virtual ~CmdSaslContinue(); virtual void addRequiredPrivileges(const std::string&, const BSONObj&, std::vector*) {} virtual bool run(OperationContext* txn, const std::string& db, BSONObj& cmdObj, int options, std::string& ignored, BSONObjBuilder& result); virtual void help(stringstream& help) const; virtual bool supportsWriteConcern(const BSONObj& cmd) const override { return false; } virtual bool slaveOk() const { return true; } virtual bool requiresAuth() { return false; } }; CmdSaslStart cmdSaslStart; CmdSaslContinue cmdSaslContinue; Status buildResponse(const SaslAuthenticationSession* session, const std::string& responsePayload, BSONType responsePayloadType, BSONObjBuilder* result) { result->appendIntOrLL(saslCommandConversationIdFieldName, session->getConversationId()); result->appendBool(saslCommandDoneFieldName, session->isDone()); if (responsePayload.size() > size_t(std::numeric_limits::max())) { return Status(ErrorCodes::InvalidLength, "Response payload too long"); } if (responsePayloadType == BinData) { result->appendBinData(saslCommandPayloadFieldName, int(responsePayload.size()), BinDataGeneral, responsePayload.data()); } else if (responsePayloadType == String) { result->append(saslCommandPayloadFieldName, base64::encode(responsePayload)); } else { fassertFailed(4003); } return Status::OK(); } Status extractConversationId(const BSONObj& cmdObj, int64_t* conversationId) { BSONElement element; Status status = bsonExtractField(cmdObj, saslCommandConversationIdFieldName, &element); if (!status.isOK()) return status; if (!element.isNumber()) { return Status(ErrorCodes::TypeMismatch, str::stream() << "Wrong type for field; expected number for " << element); } *conversationId = element.numberLong(); return Status::OK(); } Status extractMechanism(const BSONObj& cmdObj, std::string* mechanism) { return bsonExtractStringField(cmdObj, saslCommandMechanismFieldName, mechanism); } void addStatus(const Status& status, BSONObjBuilder* builder) { builder->append("ok", status.isOK() ? 1.0 : 0.0); if (!status.isOK()) builder->append(saslCommandCodeFieldName, status.code()); if (!status.reason().empty()) builder->append(saslCommandErrmsgFieldName, status.reason()); } Status doSaslStep(const ClientBasic* client, SaslAuthenticationSession* 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()) { const SockAddr clientAddr = client->port()->remoteAddr(); log() << session->getMechanism() << " authentication failed for " << session->getPrincipalId() << " on " << session->getAuthenticationDatabase() << " from client " << clientAddr.getAddr() << " ; " << status.toString() << std::endl; sleepmillis(saslGlobalParams.authFailedDelay); // All the client needs to know is that authentication has failed. return Status(ErrorCodes::AuthenticationFailed, "Authentication failed."); } status = buildResponse(session, responsePayload, type, result); if (!status.isOK()) return status; if (session->isDone()) { UserName userName(session->getPrincipalId(), session->getAuthenticationDatabase()); status = session->getAuthorizationSession()->addAndAuthorizeUser(session->getOpCtxt(), userName); if (!status.isOK()) { return status; } if (!serverGlobalParams.quiet) { log() << "Successfully authenticated as principal " << session->getPrincipalId() << " on " << session->getAuthenticationDatabase(); } } return Status::OK(); } Status doSaslStart(const ClientBasic* client, SaslAuthenticationSession* session, 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); 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); } status = session->start( db, mechanism, saslGlobalParams.serviceName, saslGlobalParams.hostName, 1, autoAuthorize); if (!status.isOK()) return status; return doSaslStep(client, session, cmdObj, result); } Status doSaslContinue(const ClientBasic* client, SaslAuthenticationSession* session, const BSONObj& cmdObj, BSONObjBuilder* result) { int64_t conversationId = 0; Status status = extractConversationId(cmdObj, &conversationId); if (!status.isOK()) return status; if (conversationId != session->getConversationId()) return Status(ErrorCodes::ProtocolError, "sasl: Mismatched conversation id"); return doSaslStep(client, session, cmdObj, result); } CmdSaslStart::CmdSaslStart() : Command(saslStartCommandName) {} CmdSaslStart::~CmdSaslStart() {} void CmdSaslStart::help(std::stringstream& os) const { os << "First step in a SASL authentication conversation."; } void CmdSaslStart::redactForLogging(mutablebson::Document* cmdObj) { mutablebson::Element element = mutablebson::findFirstChildNamed(cmdObj->root(), "payload"); if (element.ok()) { element.setValueString("xxx"); } } bool CmdSaslStart::run(OperationContext* txn, const std::string& db, BSONObj& cmdObj, int options, std::string& ignored, BSONObjBuilder& result) { ClientBasic* client = ClientBasic::getCurrent(); AuthenticationSession::set(client, std::unique_ptr()); std::string mechanism; if (!extractMechanism(cmdObj, &mechanism).isOK()) { return false; } SaslAuthenticationSession* session = SaslAuthenticationSession::create(AuthorizationSession::get(client), mechanism); std::unique_ptr sessionGuard(session); session->setOpCtxt(txn); Status status = doSaslStart(client, session, db, cmdObj, &result); addStatus(status, &result); if (session->isDone()) { audit::logAuthentication(client, session->getMechanism(), UserName(session->getPrincipalId(), db), status.code()); } else { AuthenticationSession::swap(client, sessionGuard); } return status.isOK(); } CmdSaslContinue::CmdSaslContinue() : Command(saslContinueCommandName) {} CmdSaslContinue::~CmdSaslContinue() {} void CmdSaslContinue::help(std::stringstream& os) const { os << "Subsequent steps in a SASL authentication conversation."; } bool CmdSaslContinue::run(OperationContext* txn, const std::string& db, BSONObj& cmdObj, int options, std::string& ignored, BSONObjBuilder& result) { ClientBasic* client = ClientBasic::getCurrent(); std::unique_ptr sessionGuard; AuthenticationSession::swap(client, sessionGuard); if (!sessionGuard || sessionGuard->getType() != AuthenticationSession::SESSION_TYPE_SASL) { addStatus(Status(ErrorCodes::ProtocolError, "No SASL session state found"), &result); return false; } SaslAuthenticationSession* session = static_cast(sessionGuard.get()); // 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) { addStatus(Status(ErrorCodes::ProtocolError, "Attempt to switch database target during SASL authentication."), &result); return false; } session->setOpCtxt(txn); Status status = doSaslContinue(client, session, cmdObj, &result); addStatus(status, &result); if (session->isDone()) { audit::logAuthentication(client, session->getMechanism(), UserName(session->getPrincipalId(), db), status.code()); } else { AuthenticationSession::swap(client, sessionGuard); } return status.isOK(); } // The CyrusSaslCommands Enterprise initializer is dependent on PreSaslCommands MONGO_INITIALIZER_WITH_PREREQUISITES(PreSaslCommands, ("NativeSaslServerCore")) (InitializerContext*) { if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-CR")) CmdAuthenticate::disableAuthMechanism("MONGODB-CR"); if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-X509")) CmdAuthenticate::disableAuthMechanism("MONGODB-X509"); // For backwards compatibility, in 3.0 we are letting MONGODB-CR imply general // challenge-response auth and hence SCRAM-SHA-1 is enabled by either specifying // SCRAM-SHA-1 or MONGODB-CR in the authenticationMechanism server parameter. if (!sequenceContains(saslGlobalParams.authenticationMechanisms, "SCRAM-SHA-1") && sequenceContains(saslGlobalParams.authenticationMechanisms, "MONGODB-CR")) saslGlobalParams.authenticationMechanisms.push_back("SCRAM-SHA-1"); return Status::OK(); } } // namespace } // namespace mongo