Status AuthorizationManager::_buildPrivilegeSetFromOldStylePrivilegeDocument( const std::string& dbname, const UserName& user, const BSONObj& privilegeDocument, PrivilegeSet* result) const { if (!(privilegeDocument.hasField(AuthorizationManager::USER_NAME_FIELD_NAME) && privilegeDocument.hasField(AuthorizationManager::PASSWORD_FIELD_NAME))) { return Status(ErrorCodes::UnsupportedFormat, mongoutils::str::stream() << "Invalid old-style privilege document " "received when trying to extract privileges: " << privilegeDocument, 0); } std::string userName = privilegeDocument[AuthorizationManager::USER_NAME_FIELD_NAME].str(); if (userName != user.getUser()) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Principal name from privilege document \"" << userName << "\" doesn't match name of provided Principal \"" << user.getUser() << "\"", 0); } bool readOnly = privilegeDocument[READONLY_FIELD_NAME].trueValue(); ActionSet actions = getActionsForOldStyleUser(dbname, readOnly); std::string resourceName = (dbname == ADMIN_DBNAME || dbname == LOCAL_DBNAME) ? PrivilegeSet::WILDCARD_RESOURCE : dbname; result->grantPrivilege(Privilege(resourceName, actions), user); return Status::OK(); }
Status AuthzManagerExternalStateMongos::getUserDescription(OperationContext* txn, const UserName& userName, BSONObj* result) { BSONObj usersInfoCmd = BSON("usersInfo" << BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB())) << "showPrivileges" << true << "showCredentials" << true); BSONObjBuilder builder; const bool ok = grid.catalogManager(txn) ->runUserManagementReadCommand(txn, "admin", usersInfoCmd, &builder); BSONObj cmdResult = builder.obj(); if (!ok) { return Command::getStatusFromCommandResult(cmdResult); } std::vector<BSONElement> foundUsers = cmdResult["users"].Array(); if (foundUsers.size() == 0) { return Status(ErrorCodes::UserNotFound, "User \"" + userName.toString() + "\" not found"); } if (foundUsers.size() > 1) { return Status(ErrorCodes::UserDataInconsistent, str::stream() << "Found multiple users on the \"" << userName.getDB() << "\" database with name \"" << userName.getUser() << "\""); } *result = foundUsers[0].Obj().getOwned(); return Status::OK(); }
Status AuthzManagerExternalState::getPrivilegeDocument(const UserName& userName, int authzVersion, BSONObj* result) { if (userName == internalSecurity.user->getName()) { return Status(ErrorCodes::InternalError, "Requested privilege document for the internal user"); } StringData dbname = userName.getDB(); // Make sure the dbname is actually a database if (dbname == StringData("$external", StringData::LiteralTag()) || dbname == AuthorizationManager::SERVER_RESOURCE_NAME || dbname == AuthorizationManager::CLUSTER_RESOURCE_NAME) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "No privilege documents stored in the " << dbname << " user source."); } if (!NamespaceString::validDBName(dbname)) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Bad database name \"" << dbname << "\""); } // Build the query needed to get the privilege document std::string usersNamespace; BSONObjBuilder queryBuilder; if (authzVersion == 1) { usersNamespace = mongoutils::str::stream() << dbname << ".system.users"; queryBuilder.append(AuthorizationManager::V1_USER_NAME_FIELD_NAME, userName.getUser()); queryBuilder.appendNull(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME); } else if (authzVersion == 2) { usersNamespace = "admin.system.users"; queryBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, userName.getUser()); queryBuilder.append(AuthorizationManager::USER_SOURCE_FIELD_NAME, userName.getDB()); } else { return Status(ErrorCodes::UnsupportedFormat, mongoutils::str::stream() << "Unrecognized authorization format version: " << authzVersion); } // Query for the privilege document BSONObj userBSONObj; Status found = _findUser(usersNamespace, queryBuilder.obj(), &userBSONObj); if (!found.isOK()) { if (found.code() == ErrorCodes::UserNotFound) { // Return more detailed status that includes user name. return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "auth: couldn't find user " << userName.toString() << ", " << usersNamespace, 0); } else { return found; } } *result = userBSONObj.getOwned(); return Status::OK(); }
Status AuthzSessionExternalState::getPrivilegeDocument(const std::string& dbname, const UserName& userName, BSONObj* result) { if (dbname == StringData("$external", StringData::LiteralTag()) || dbname == AuthorizationManager::SERVER_RESOURCE_NAME || dbname == AuthorizationManager::CLUSTER_RESOURCE_NAME) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "No privilege documents stored in the " << dbname << " user source."); } if (!NamespaceString::validDBName(dbname)) { return Status(ErrorCodes::BadValue, "Bad database name \"" + dbname + "\""); } if (dbname == StringData("local", StringData::LiteralTag()) && userName.getUser() == internalSecurity.user) { if (internalSecurity.pwd.empty()) { return Status(ErrorCodes::UserNotFound, "key file must be used to log in with internal user", 15889); } *result = BSON(AuthorizationManager::USER_NAME_FIELD_NAME << internalSecurity.user << AuthorizationManager::PASSWORD_FIELD_NAME << internalSecurity.pwd).getOwned(); return Status::OK(); } std::string usersNamespace = dbname + ".system.users"; BSONObj userBSONObj; BSONObjBuilder queryBuilder; queryBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, userName.getUser()); if (userName.getDB() == dbname) { queryBuilder.appendNull(AuthorizationManager::USER_SOURCE_FIELD_NAME); } else { queryBuilder.append(AuthorizationManager::USER_SOURCE_FIELD_NAME, userName.getDB()); } bool found = _findUser(usersNamespace, queryBuilder.obj(), &userBSONObj); if (!found) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "auth: couldn't find user " << userName.toString() << ", " << usersNamespace, 0); } *result = userBSONObj.getOwned(); return Status::OK(); }
bool CmdAuthenticate::run(OperationContext* opCtx, const string& dbname, const BSONObj& cmdObj, BSONObjBuilder& result) { if (!serverGlobalParams.quiet.load()) { mutablebson::Document cmdToLog(cmdObj, mutablebson::Document::kInPlaceDisabled); redactForLogging(&cmdToLog); log() << " authenticate db: " << dbname << " " << cmdToLog; } std::string mechanism = cmdObj.getStringField("mechanism"); if (mechanism.empty()) { mechanism = "MONGODB-CR"; } UserName user; auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session()); if (mechanism == "MONGODB-X509" && !cmdObj.hasField("user")) { user = UserName(sslPeerInfo.subjectName, dbname); } else { user = UserName(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(); } Status status = _authenticate(opCtx, mechanism, user, cmdObj); audit::logAuthentication(Client::getCurrent(), mechanism, user, status.code()); if (!status.isOK()) { if (!serverGlobalParams.quiet.load()) { auto const client = opCtx->getClient(); log() << "Failed to authenticate " << user << (client->hasRemote() ? (" from client " + client->getRemote().toString()) : "") << " 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); } sleepmillis(saslGlobalParams.authFailedDelay.load()); return false; } result.append("dbname", user.getDB()); result.append("user", user.getUser()); return true; }
Status AuthzManagerExternalStateMongod::_getUserDocument(const UserName& userName, BSONObj* userDoc) { Client::ReadContext ctx("admin"); int authzVersion; Status status = getStoredAuthorizationVersion(&authzVersion); if (!status.isOK()) return status; switch (authzVersion) { case AuthorizationManager::schemaVersion26Upgrade: case AuthorizationManager::schemaVersion26Final: break; default: return Status(ErrorCodes::AuthSchemaIncompatible, mongoutils::str::stream() << "Unsupported schema version for getUserDescription(): " << authzVersion); } status = findOne( (authzVersion == AuthorizationManager::schemaVersion26Final ? AuthorizationManager::usersCollectionNamespace : AuthorizationManager::usersAltCollectionNamespace), BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), userDoc); if (status == ErrorCodes::NoMatchingDocument) { status = Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "Could not find user " << userName.getFullName()); } return status; }
Status AuthzManagerExternalStateMongos::updatePrivilegeDocument( const UserName& user, const BSONObj& updateObj) { try { string userNS = mongoutils::str::stream() << user.getDB() << ".system.users"; scoped_ptr<ScopedDbConnection> conn(getConnectionForUsersCollection(userNS)); conn->get()->update(userNS, QUERY("user" << user.getUser() << "userSource" << BSONNULL), updateObj); // 30 second timeout for w:majority BSONObj res = conn->get()->getLastErrorDetailed(false, false, -1, 30*1000); string err = conn->get()->getLastErrorString(res); conn->done(); if (!err.empty()) { return Status(ErrorCodes::UserModificationFailed, err); } int numUpdated = res["n"].numberInt(); dassert(numUpdated <= 1 && numUpdated >= 0); if (numUpdated == 0) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "User " << user.getFullName() << " not found"); } return Status::OK(); } catch (const DBException& e) { return e.toStatus(); } }
Status AuthzManagerExternalStateMongod::updatePrivilegeDocument( const UserName& user, const BSONObj& updateObj) const { string userNS = mongoutils::str::stream() << user.getDB() << ".system.users"; Client::GodScope gs; Client::WriteContext ctx(userNS); DBDirectClient client; client.update(userNS, QUERY("user" << user.getUser() << "userSource" << BSONNULL), updateObj); // 30 second timeout for w:majority BSONObj res = client.getLastErrorDetailed(false, false, -1, 30*1000); string err = client.getLastErrorString(res); if (!err.empty()) { return Status(ErrorCodes::UserModificationFailed, err); } int numUpdated = res["n"].numberInt(); dassert(numUpdated <= 1 && numUpdated >= 0); if (numUpdated == 0) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "User " << user.getFullName() << " not found"); } return Status::OK(); }
Status AuthzManagerExternalStateMongos::getUserDescription(const UserName& userName, BSONObj* result) { try { scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection( AuthorizationManager::usersCollectionNamespace)); BSONObj cmdResult; conn->get()->runCommand( "admin", BSON("usersInfo" << BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_SOURCE_FIELD_NAME << userName.getDB())) << "showPrivileges" << true << "showCredentials" << true), cmdResult); if (!cmdResult["ok"].trueValue()) { int code = cmdResult["code"].numberInt(); if (code == 0) code = ErrorCodes::UnknownError; return Status(ErrorCodes::Error(code), cmdResult["errmsg"].str()); } *result = cmdResult["users"]["0"].Obj().getOwned(); conn->done(); return Status::OK(); } catch (const DBException& e) { return e.toStatus(); } }
Status AuthzManagerExternalState::getPrivilegeDocumentV1(const StringData& dbname, const UserName& userName, BSONObj* result) { if (userName == internalSecurity.user->getName()) { return Status(ErrorCodes::InternalError, "Requested privilege document for the internal user"); } if (!NamespaceString::validDBName(dbname)) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Bad database name \"" << dbname << "\""); } const bool isUserFromTargetDB = (dbname == userName.getDB()); // Build the query needed to get the privilege document BSONObjBuilder queryBuilder; const NamespaceString usersNamespace(dbname, "system.users"); queryBuilder.append(AuthorizationManager::V1_USER_NAME_FIELD_NAME, userName.getUser()); if (isUserFromTargetDB) { queryBuilder.appendNull(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME); } else { queryBuilder.append(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME, userName.getDB()); } // Query for the privilege document BSONObj userBSONObj; Status found = findOne(usersNamespace, queryBuilder.done(), &userBSONObj); if (!found.isOK()) { if (found.code() == ErrorCodes::NoMatchingDocument) { // Return more detailed status that includes user name. return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "auth: couldn't find user " << userName.toString() << ", " << usersNamespace.ns(), 0); } else { return found; } } if (isUserFromTargetDB) { if (userBSONObj[AuthorizationManager::PASSWORD_FIELD_NAME].eoo()) { return Status(ErrorCodes::AuthSchemaIncompatible, mongoutils::str::stream() << "User documents with schema version " << AuthorizationManager::schemaVersion24 << " must have a \"" << AuthorizationManager::PASSWORD_FIELD_NAME << "\" field."); } } *result = userBSONObj.getOwned(); return Status::OK(); }
void SASLServerMechanismRegistry::advertiseMechanismNamesForUser(OperationContext* opCtx, const BSONObj& isMasterCmd, BSONObjBuilder* builder) { BSONElement saslSupportedMechs = isMasterCmd["saslSupportedMechs"]; if (saslSupportedMechs.type() == BSONType::String) { UserName userName = uassertStatusOK(UserName::parse(saslSupportedMechs.String())); // Authenticating the __system@local user to the admin database on mongos is required // by the auth passthrough test suite. if (getTestCommandsEnabled() && userName.getUser() == internalSecurity.user->getName().getUser() && userName.getDB() == "admin") { userName = internalSecurity.user->getName(); } AuthorizationManager* authManager = AuthorizationManager::get(opCtx->getServiceContext()); UserHandle user; const auto swUser = authManager->acquireUser(opCtx, userName); if (!swUser.isOK()) { auto& status = swUser.getStatus(); if (status.code() == ErrorCodes::UserNotFound) { log() << "Supported SASL mechanisms requested for unknown user '" << userName << "'"; return; } uassertStatusOK(status); } user = std::move(swUser.getValue()); BSONArrayBuilder mechanismsBuilder; const auto& mechList = _getMapRef(userName.getDB()); for (const auto& factoryIt : mechList) { SecurityPropertySet properties = factoryIt->properties(); if (!properties.hasAllProperties(SecurityPropertySet{SecurityProperty::kNoPlainText, SecurityProperty::kMutualAuth}) && userName.getDB() != "$external") { continue; } auto mechanismEnabled = _mechanismSupportedByConfig(factoryIt->mechanismName()); if (!mechanismEnabled && userName == internalSecurity.user->getName()) { mechanismEnabled = factoryIt->isInternalAuthMech(); } if (mechanismEnabled && factoryIt->canMakeMechanismForUser(user.get())) { mechanismsBuilder << factoryIt->mechanismName(); } } builder->appendArray("saslSupportedMechs", mechanismsBuilder.arr()); } }
Status CmdAuthenticate::_authenticateX509(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 (user.getUser() != subjectName) { return Status(ErrorCodes::AuthenticationFailed, "There is no x.509 client certificate matching the user."); } else { std::string srvSubjectName = getSSLManager()->getServerSubjectName(); size_t srvClusterIdPos = srvSubjectName.find(",OU="); size_t peerClusterIdPos = subjectName.find(",OU="); std::string srvClusterId = srvClusterIdPos != std::string::npos ? srvSubjectName.substr(srvClusterIdPos) : ""; std::string peerClusterId = peerClusterIdPos != std::string::npos ? subjectName.substr(peerClusterIdPos) : ""; // Handle internal cluster member auth, only applies to server-server connections int clusterAuthMode = serverGlobalParams.clusterAuthMode.load(); if (srvClusterId == peerClusterId && !srvClusterId.empty()) { 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(user); if (!status.isOK()) { return status; } } return Status::OK(); } }
Status AuthzManagerExternalStateMongos::getUserDescription( OperationContext* txn, const UserName& userName, BSONObj* result) { try { scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection( AuthorizationManager::usersCollectionNamespace)); BSONObj cmdResult; conn->get()->runCommand( "admin", BSON("usersInfo" << BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB())) << "showPrivileges" << true << "showCredentials" << true), cmdResult); if (!cmdResult["ok"].trueValue()) { int code = cmdResult["code"].numberInt(); if (code == 0) code = ErrorCodes::UnknownError; return Status(ErrorCodes::Error(code), cmdResult["errmsg"].str()); } std::vector<BSONElement> foundUsers = cmdResult["users"].Array(); if (foundUsers.size() == 0) { return Status(ErrorCodes::UserNotFound, "User \"" + userName.toString() + "\" not found"); } if (foundUsers.size() > 1) { return Status(ErrorCodes::UserDataInconsistent, mongoutils::str::stream() << "Found multiple users on the \"" << userName.getDB() << "\" database with name \"" << userName.getUser() << "\""); } *result = foundUsers[0].Obj().getOwned(); conn->done(); return Status::OK(); } catch (const DBException& e) { return e.toStatus(); } }
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 = AuthorizationSession::get(client); 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(); } }
Status AuthorizationSession::acquirePrivilege(const Privilege& privilege, const UserName& authorizingUser) { if (!_authenticatedPrincipals.lookup(authorizingUser)) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "No authenticated user found with name: " << authorizingUser.getUser() << " from database " << authorizingUser.getDB(), 0); } _acquiredPrivileges.grantPrivilege(privilege, authorizingUser); return Status::OK(); }
bool run(const string& dbname, BSONObj& cmdObj, int options, string& errmsg, BSONObjBuilder& result, bool fromRepl) { AuthorizationManager* authzManager = getGlobalAuthorizationManager(); AuthzDocumentsUpdateGuard updateGuard(authzManager); if (!updateGuard.tryLock("Remove user")) { addStatus(Status(ErrorCodes::LockBusy, "Could not lock auth data update lock."), result); return false; } UserName userName; BSONObj writeConcern; Status status = auth::parseAndValidateRemoveUserCommand(cmdObj, dbname, &userName, &writeConcern); if (!status.isOK()) { addStatus(status, result); return false; } int numUpdated; status = authzManager->removePrivilegeDocuments( BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_SOURCE_FIELD_NAME << userName.getDB()), writeConcern, &numUpdated); // Must invalidate even on bad status - what if the write succeeded but the GLE failed? authzManager->invalidateUserByName(userName); if (!status.isOK()) { addStatus(status, result); return false; } if (numUpdated == 0) { addStatus(Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "User '" << userName.getFullName() << "' not found"), result); return false; } return true; }
Status AuthzManagerExternalStateLocal::_getUserDocument(OperationContext* txn, const UserName& userName, BSONObj* userDoc) { Status status = findOne( txn, AuthorizationManager::usersCollectionNamespace, BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB()), userDoc); if (status == ErrorCodes::NoMatchingDocument) { status = Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "Could not find user " << userName.getFullName()); } return status; }
Status AuthorizationSession::acquirePrivilegesFromPrivilegeDocument( const std::string& dbname, const UserName& user, const BSONObj& privilegeDocument) { if (!_authenticatedPrincipals.lookup(user)) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "No authenticated principle found with name: " << user.getUser() << " from database " << user.getDB(), 0); } return _externalState->getAuthorizationManager().buildPrivilegeSet(dbname, user, privilegeDocument, &_acquiredPrivileges); }
Status AuthzManagerExternalState::getPrivilegeDocumentV1(const StringData& dbname, const UserName& userName, BSONObj* result) { if (userName == internalSecurity.user->getName()) { return Status(ErrorCodes::InternalError, "Requested privilege document for the internal user"); } if (!NamespaceString::validDBName(dbname)) { return Status(ErrorCodes::BadValue, mongoutils::str::stream() << "Bad database name \"" << dbname << "\""); } // Build the query needed to get the privilege document std::string usersNamespace; BSONObjBuilder queryBuilder; usersNamespace = mongoutils::str::stream() << dbname << ".system.users"; queryBuilder.append(AuthorizationManager::V1_USER_NAME_FIELD_NAME, userName.getUser()); if (dbname == userName.getDB()) { queryBuilder.appendNull(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME); } else { queryBuilder.append(AuthorizationManager::V1_USER_SOURCE_FIELD_NAME, userName.getDB()); } // Query for the privilege document BSONObj userBSONObj; Status found = _findUser(usersNamespace, queryBuilder.done(), &userBSONObj); if (!found.isOK()) { if (found.code() == ErrorCodes::UserNotFound) { // Return more detailed status that includes user name. return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "auth: couldn't find user " << userName.toString() << ", " << usersNamespace, 0); } else { return found; } } *result = userBSONObj.getOwned(); return Status::OK(); }
Status AuthzManagerExternalStateMongod::updatePrivilegeDocument( const UserName& user, const BSONObj& updateObj, const BSONObj& writeConcern) { try { const std::string userNS = "admin.system.users"; DBDirectClient client; { Client::GodScope gs; // TODO(spencer): Once we're no longer fully rebuilding the user cache on every // change to user data we should remove the global lock and uncomment the // WriteContext below Lock::GlobalWrite w; // Client::WriteContext ctx(userNS); client.update(userNS, QUERY(AuthorizationManager::USER_NAME_FIELD_NAME << user.getUser() << AuthorizationManager::USER_SOURCE_FIELD_NAME << user.getDB()), updateObj); } // Handle write concern BSONObjBuilder gleBuilder; gleBuilder.append("getLastError", 1); gleBuilder.appendElements(writeConcern); BSONObj res; client.runCommand("admin", gleBuilder.done(), res); string err = client.getLastErrorString(res); if (!err.empty()) { return Status(ErrorCodes::UserModificationFailed, err); } int numUpdated = res["n"].numberInt(); dassert(numUpdated <= 1 && numUpdated >= 0); if (numUpdated == 0) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "User " << user.getFullName() << " not found"); } return Status::OK(); } catch (const DBException& e) { return e.toStatus(); } }
Status AuthzManagerExternalState::updatePrivilegeDocument( const UserName& user, const BSONObj& updateObj, const BSONObj& writeConcern) { Status status = updateOne( NamespaceString("admin.system.users"), BSON(AuthorizationManager::USER_NAME_FIELD_NAME << user.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << user.getDB()), updateObj, false, writeConcern); if (status.isOK()) { return status; } if (status.code() == ErrorCodes::NoMatchingDocument) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "User " << user.getFullName() << " not found"); } if (status.code() == ErrorCodes::UnknownError) { return Status(ErrorCodes::UserModificationFailed, status.reason()); } return status; }
Status AuthzManagerExternalStateMongos::getUserDescription(const UserName& userName, BSONObj* result) { try { scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection( AuthorizationManager::usersCollectionNamespace)); BSONObj cmdResult; conn->get()->runCommand( userName.getDB().toString(), // TODO: Change usersInfo so this command can always go to "admin". BSON("usersInfo" << userName.getUser() << "details" << true), cmdResult); if (!cmdResult["ok"].trueValue()) { int code = cmdResult["code"].numberInt(); if (code == 0) code = ErrorCodes::UnknownError; return Status(ErrorCodes::Error(code), cmdResult["errmsg"].str()); } *result = cmdResult["users"]["0"].Obj().getOwned(); conn->done(); return Status::OK(); } catch (const DBException& e) { return e.toStatus(); } }
Status AuthzManagerExternalStateMongos::updatePrivilegeDocument( const UserName& user, const BSONObj& updateObj, const BSONObj& writeConcern) { try { const std::string userNS = "admin.system.users"; scoped_ptr<ScopedDbConnection> conn(getConnectionForAuthzCollection(userNS)); conn->get()->update( userNS, QUERY(AuthorizationManager::USER_NAME_FIELD_NAME << user.getUser() << AuthorizationManager::USER_SOURCE_FIELD_NAME << user.getDB()), updateObj); // Handle write concern BSONObjBuilder gleBuilder; gleBuilder.append("getLastError", 1); gleBuilder.appendElements(writeConcern); BSONObj res; conn->get()->runCommand("admin", gleBuilder.done(), res); string err = conn->get()->getLastErrorString(res); conn->done(); if (!err.empty()) { return Status(ErrorCodes::UserModificationFailed, err); } int numUpdated = res["n"].numberInt(); dassert(numUpdated <= 1 && numUpdated >= 0); if (numUpdated == 0) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "User " << user.getFullName() << " not found"); } return Status::OK(); } catch (const DBException& e) { return e.toStatus(); } }
Status AuthzManagerExternalStateMongod::updatePrivilegeDocument( const UserName& user, const BSONObj& updateObj) { try { string userNS = mongoutils::str::stream() << user.getDB() << ".system.users"; DBDirectClient client; { Client::GodScope gs; // TODO(spencer): Once we're no longer fully rebuilding the user cache on every // change to user data we should remove the global lock and uncomment the // WriteContext below Lock::GlobalWrite w; // Client::WriteContext ctx(userNS); client.update(userNS, QUERY("user" << user.getUser() << "userSource" << BSONNULL), updateObj); } // 30 second timeout for w:majority BSONObj res = client.getLastErrorDetailed(false, false, -1, 30*1000); string err = client.getLastErrorString(res); if (!err.empty()) { return Status(ErrorCodes::UserModificationFailed, err); } int numUpdated = res["n"].numberInt(); dassert(numUpdated <= 1 && numUpdated >= 0); if (numUpdated == 0) { return Status(ErrorCodes::UserNotFound, mongoutils::str::stream() << "User " << user.getFullName() << " not found"); } return Status::OK(); } catch (const DBException& e) { return e.toStatus(); } }
Status CmdAuthenticate::_authenticateX509(const UserName& user, const BSONObj& cmdObj) { 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(); StringData subjectName = client->port()->getX509SubjectName(); if (user.getUser() != subjectName) { return Status(ErrorCodes::AuthenticationFailed, "There is no x.509 client certificate matching the user."); } else { StringData srvSubjectName = getSSLManager()->getServerSubjectName(); StringData srvClusterId = srvSubjectName.substr(srvSubjectName.find(",OU=")); StringData peerClusterId = subjectName.substr(subjectName.find(",OU=")); fassert(17002, !srvClusterId.empty() && srvClusterId != srvSubjectName); // Handle internal cluster member auth, only applies to server-server connections if (srvClusterId == peerClusterId) { if (cmdLine.clusterAuthMode.empty() || cmdLine.clusterAuthMode == "keyfile") { return Status(ErrorCodes::AuthenticationFailed, "X509 authentication is not allowed for cluster authentication"); } authorizationSession->grantInternalAuthorization(user); } // Handle normal client authentication, only applies to client-server connections else { Principal* principal = new Principal(user); authorizationSession->addAndAuthorizePrincipal(principal); } return Status::OK(); } }
Status CmdAuthenticate::_authenticateCR(const UserName& user, const BSONObj& cmdObj) { if (user == internalSecurity.user->getName() && serverGlobalParams.clusterAuthMode == "x509") { return Status(ErrorCodes::AuthenticationFailed, "Mechanism x509 is required for internal cluster authentication"); } if (!_areNonceAuthenticateCommandsEnabled) { // 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, _nonceAuthenticateCommandsDisabledMessage); } } 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<AuthenticationSession> 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<MongoAuthenticationSession*>(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(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(user); if (!status.isOK()) { return status; } return Status::OK(); }
Status AuthzManagerExternalStateMongod::getUserDescription(const UserName& userName, BSONObj* result) { BSONObj userDoc; Status status = _findUser( "admin.system.users", BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_SOURCE_FIELD_NAME << userName.getDB()), &userDoc); if (!status.isOK()) return status; BSONElement directRolesElement; status = bsonExtractTypedField(userDoc, "roles", Array, &directRolesElement); if (!status.isOK()) return status; std::vector<User::RoleData> directRoles; status = V2UserDocumentParser::parseRoleVector(BSONArray(directRolesElement.Obj()), &directRoles); if (!status.isOK()) return status; unordered_set<RoleName> indirectRoles; PrivilegeVector allPrivileges; { boost::lock_guard<boost::mutex> lk(_roleGraphMutex); for (size_t i = 0; i < directRoles.size(); ++i) { const User::RoleData& role(directRoles[i]); if (!role.hasRole) continue; indirectRoles.insert(role.name); if (_roleGraphState == roleGraphStateConsistent) { for (RoleNameIterator subordinates = _roleGraph.getIndirectSubordinates( role.name); subordinates.more(); subordinates.next()) { indirectRoles.insert(subordinates.get()); } } const PrivilegeVector& rolePrivileges( (_roleGraphState == roleGraphStateConsistent) ? _roleGraph.getAllPrivileges(role.name) : _roleGraph.getDirectPrivileges(role.name)); for (PrivilegeVector::const_iterator priv = rolePrivileges.begin(), end = rolePrivileges.end(); priv != end; ++priv) { Privilege::addPrivilegeToPrivilegeVector(&allPrivileges, *priv); } } } mutablebson::Document resultDoc(userDoc, mutablebson::Document::kInPlaceDisabled); mutablebson::Element indirectRolesElement = resultDoc.makeElementArray("indirectRoles"); mutablebson::Element privilegesElement = resultDoc.makeElementArray("privileges"); mutablebson::Element warningsElement = resultDoc.makeElementArray("warnings"); fassert(17158, resultDoc.root().pushBack(privilegesElement)); fassert(17159, resultDoc.root().pushBack(indirectRolesElement)); if (_roleGraphState != roleGraphStateConsistent) { fassert(17160, warningsElement.appendString( "", "Role graph inconsistent, only direct privileges available.")); } addRoleNameObjectsToArrayElement(indirectRolesElement, makeRoleNameIteratorForContainer(indirectRoles)); addPrivilegeObjectsOrWarningsToArrayElement( privilegesElement, warningsElement, allPrivileges); if (warningsElement.hasChildren()) { fassert(17161, resultDoc.root().pushBack(warningsElement)); } *result = resultDoc.getObject(); return Status::OK(); }
Status AuthzManagerExternalStateMongos::getUserDescription(OperationContext* opCtx, const UserName& userName, BSONObj* result) { if (!shouldUseRolesFromConnection(opCtx, userName)) { BSONObj usersInfoCmd = BSON("usersInfo" << BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME << userName.getUser() << AuthorizationManager::USER_DB_FIELD_NAME << userName.getDB())) << "showPrivileges" << true << "showCredentials" << true); BSONObjBuilder builder; const bool ok = Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand( opCtx, "admin", usersInfoCmd, &builder); BSONObj cmdResult = builder.obj(); if (!ok) { return getStatusFromCommandResult(cmdResult); } std::vector<BSONElement> foundUsers = cmdResult["users"].Array(); if (foundUsers.size() == 0) { return Status(ErrorCodes::UserNotFound, "User \"" + userName.toString() + "\" not found"); } if (foundUsers.size() > 1) { return Status(ErrorCodes::UserDataInconsistent, str::stream() << "Found multiple users on the \"" << userName.getDB() << "\" database with name \"" << userName.getUser() << "\""); } *result = foundUsers[0].Obj().getOwned(); return Status::OK(); } else { // Obtain privilege information from the config servers for all roles acquired from the X509 // certificate. BSONArrayBuilder userRolesBuilder; auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session()); for (const RoleName& role : sslPeerInfo.roles) { userRolesBuilder.append(BSON(AuthorizationManager::ROLE_NAME_FIELD_NAME << role.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << role.getDB())); } BSONArray providedRoles = userRolesBuilder.arr(); BSONObj rolesInfoCmd = BSON("rolesInfo" << providedRoles << "showPrivileges" << "asUserFragment"); BSONObjBuilder cmdResultBuilder; const bool cmdOk = Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand( opCtx, "admin", rolesInfoCmd, &cmdResultBuilder); BSONObj cmdResult = cmdResultBuilder.obj(); if (!cmdOk || !cmdResult["userFragment"].ok()) { return Status(ErrorCodes::FailedToParse, "Unable to get resolved X509 roles from config server: " + getStatusFromCommandResult(cmdResult).toString()); } cmdResult = cmdResult["userFragment"].Obj().getOwned(); BSONElement userRoles = cmdResult["roles"]; BSONElement userInheritedRoles = cmdResult["inheritedRoles"]; BSONElement userInheritedPrivileges = cmdResult["inheritedPrivileges"]; if (userRoles.eoo() || userInheritedRoles.eoo() || userInheritedPrivileges.eoo() || !userRoles.isABSONObj() || !userInheritedRoles.isABSONObj() || !userInheritedPrivileges.isABSONObj()) { return Status( ErrorCodes::UserDataInconsistent, "Recieved malformed response to request for X509 roles from config server"); } *result = BSON("_id" << userName.getUser() << "user" << userName.getUser() << "db" << userName.getDB() << "credentials" << BSON("external" << true) << "roles" << BSONArray(cmdResult["roles"].Obj()) << "inheritedRoles" << BSONArray(cmdResult["inheritedRoles"].Obj()) << "inheritedPrivileges" << BSONArray(cmdResult["inheritedPrivileges"].Obj())); return Status::OK(); } }