diff options
author | Andreas Nilsson <andreas.nilsson@10gen.com> | 2014-09-12 11:29:50 -0400 |
---|---|---|
committer | Andreas Nilsson <andreas.nilsson@10gen.com> | 2014-09-17 15:44:49 -0700 |
commit | dedf038860746ad0bed3a6f37ae702587933b5f8 (patch) | |
tree | 4c7939436e56c4953ae71d55ccd4d887015cf9d6 /src | |
parent | 859876746f3abcf7a97e150e1179cc70325ea274 (diff) | |
download | mongo-dedf038860746ad0bed3a6f37ae702587933b5f8.tar.gz |
SERVER-15179 Auth upgrade from MONGODB-CR -> SCRAM-SHA-1
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 112 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 11 | ||||
-rw-r--r-- | src/mongo/db/auth/native_sasl_authentication_session.cpp | 6 | ||||
-rw-r--r-- | src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp | 12 | ||||
-rw-r--r-- | src/mongo/db/commands/auth_schema_upgrade_d.cpp | 132 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 56 | ||||
-rw-r--r-- | src/mongo/db/db.cpp | 2 | ||||
-rw-r--r-- | src/mongo/s/commands/auth_schema_upgrade_s.cpp | 2 |
8 files changed, 243 insertions, 90 deletions
diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index 6946a27bfc3..1d1dca82a71 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -30,6 +30,7 @@ #include "mongo/db/auth/authorization_manager.h" +#include <boost/bind.hpp> #include <boost/thread/mutex.hpp> #include <memory> #include <string> @@ -44,6 +45,7 @@ #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/authz_documents_update_guard.h" #include "mongo/db/auth/authz_manager_external_state.h" +#include "mongo/db/auth/mechanism_scram.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/auth/role_graph.h" #include "mongo/db/auth/user.h" @@ -721,6 +723,106 @@ namespace mongo { return _externalState->releaseAuthzUpdateLock(); } +namespace { + + /** + * Logs that the auth schema upgrade failed because of "status" and returns "status". + */ + Status logUpgradeFailed(const Status& status) { + log() << "Auth schema upgrade failed with " << status; + return status; + } + + /** + * Updates a single user document from MONGODB-CR to SCRAM credentials. + * + * Throws a DBException on errors. + */ + void updateUserCredentials(OperationContext* txn, + AuthzManagerExternalState* externalState, + const StringData& sourceDB, + const BSONObj& userDoc, + const BSONObj& writeConcern) { + BSONElement credentialsElement = userDoc["credentials"]; + uassert(18743, + mongoutils::str::stream() << "While preparing to upgrade user doc from " + "2.6/2.8 user data schema to the 2.8 SCRAM only schema, found a user doc " + "with missing or incorrectly formatted credentials: " + << userDoc.toString(), + credentialsElement.type() == Object); + + BSONObj credentialsObj = credentialsElement.Obj(); + BSONElement mongoCRElement = credentialsObj["MONGODB-CR"]; + BSONElement scramElement = credentialsObj["SCRAM-SHA-1"]; + + // Ignore any user documents that already have SCRAM credentials. This should only + // occur if a previous authSchemaUpgrade was interrupted halfway. + if (!scramElement.eoo()) { + return; + } + + uassert(18744, + mongoutils::str::stream() << "While preparing to upgrade user doc from " + "2.6/2.8 user data schema to the 2.8 SCRAM only schema, found a user doc " + "missing MONGODB-CR credentials :" + << userDoc.toString(), + !mongoCRElement.eoo()); + + std::string hashedPassword = mongoCRElement.String(); + + BSONObj query = BSON("_id" << userDoc["_id"].String()); + BSONObjBuilder updateBuilder; + { + BSONObjBuilder toSetBuilder(updateBuilder.subobjStart("$set")); + toSetBuilder << "credentials" << + BSON("SCRAM-SHA-1" << scram::generateCredentials(hashedPassword)); + } + + uassertStatusOK(externalState->updateOne(txn, + NamespaceString("admin", "system.users"), + query, + updateBuilder.obj(), + true, + writeConcern)); + } + + /** Loop through all the user documents in the admin.system.users collection. + * For each user document: + * 1. Compute SCRAM credentials based on the MONGODB-CR hash + * 2. Remove the MONGODB-CR hash + * 3. Add SCRAM credentials to the user document credentials section + */ + Status updateCredentials( + OperationContext* txn, + AuthzManagerExternalState* externalState, + const BSONObj& writeConcern) { + + // Loop through and update the user documents in admin.system.users. + Status status = externalState->query( + txn, + NamespaceString("admin", "system.users"), + BSONObj(), + BSONObj(), + boost::bind(updateUserCredentials, txn, externalState, "admin", _1, writeConcern)); + if (!status.isOK()) + return logUpgradeFailed(status); + + // Update the schema version document. + status = externalState->updateOne( + txn, + AuthorizationManager::versionCollectionNamespace, + AuthorizationManager::versionDocumentQuery, + BSON("$set" << BSON(AuthorizationManager::schemaVersionFieldName << + AuthorizationManager::schemaVersion28SCRAM)), + true, + writeConcern); + if (!status.isOK()) + return logUpgradeFailed(status); + + return Status::OK(); + } +} //namespace + Status AuthorizationManager::upgradeSchemaStep( OperationContext* txn, const BSONObj& writeConcern, bool* isDone) { int authzVersion; @@ -728,9 +830,15 @@ namespace mongo { if (!status.isOK()) { return status; } - + switch (authzVersion) { - case schemaVersion26Final: + case schemaVersion26Final: { + Status status = updateCredentials(txn, _externalState.get(), writeConcern); + if (status.isOK()) + *isDone = true; + return status; + } + case schemaVersion28SCRAM: *isDone = true; return Status::OK(); default: diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index b45712847d0..600a2ef032a 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -121,11 +121,18 @@ namespace mongo { static const int schemaVersion26Upgrade = 2; /** - * Auth schema version for MongoDB 2.6. Users are stored in admin.system.users, - * roles in admin.system.roles. + * Auth schema version for MongoDB 2.6 and 2.8 MONGODB-CR/SCRAM mixed auth mode. + * Users are stored in admin.system.users, roles in admin.system.roles. */ static const int schemaVersion26Final = 3; + /** + * Auth schema version for MongoDB 2.8 SCRAM only mode. + * Users are stored in admin.system.users, roles in admin.system.roles. + * MONGODB-CR credentials have been replaced with SCRAM credentials in the user documents. + */ + static const int schemaVersion28SCRAM = 4; + // TODO: Make the following functions no longer static. /** diff --git a/src/mongo/db/auth/native_sasl_authentication_session.cpp b/src/mongo/db/auth/native_sasl_authentication_session.cpp index b9b62d70c3f..96a7ff16c1b 100644 --- a/src/mongo/db/auth/native_sasl_authentication_session.cpp +++ b/src/mongo/db/auth/native_sasl_authentication_session.cpp @@ -143,6 +143,12 @@ namespace { Status NativeSaslAuthenticationSession::step(const StringData& inputData, std::string* outputData) { + if (!_saslConversation) { + return Status(ErrorCodes::BadValue, + mongoutils::str::stream() << + "The authentication session has not been properly initialized"); + } + StatusWith<bool> status = _saslConversation->step(inputData, outputData); if (status.isOK()) { _done = status.getValue(); diff --git a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp index cddab8001b7..373093a26fc 100644 --- a/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp +++ b/src/mongo/db/auth/sasl_scramsha1_server_conversation.cpp @@ -41,6 +41,7 @@ #include "mongo/db/auth/mechanism_scram.h" #include "mongo/platform/random.h" #include "mongo/util/base64.h" +#include "mongo/util/log.h" #include "mongo/util/mongoutils/str.h" #include "mongo/util/password_digest.h" #include "mongo/util/text.h" @@ -162,7 +163,16 @@ namespace mongo { _creds = userObj->getCredentials(); _saslAuthSession->getAuthorizationSession()->getAuthorizationManager(). releaseUser(userObj); - + + // Generate SCRAM credentials on the fly for mixed MONGODB-CR/SCRAM mode. + if (_creds.scram.salt.empty() && !_creds.password.empty()) { + BSONObj scramCreds = scram::generateCredentials(_creds.password); + _creds.scram.iterationCount = scramCreds["iterationCount"].Int(); + _creds.scram.salt = scramCreds["salt"].String(); + _creds.scram.storedKey = scramCreds["storedKey"].String(); + _creds.scram.serverKey = scramCreds["serverKey"].String(); + } + // Generate server-first-message // Create text-based nonce as base64 encoding of a binary blob of length multiple of 3 const int nonceLenQWords = 3; diff --git a/src/mongo/db/commands/auth_schema_upgrade_d.cpp b/src/mongo/db/commands/auth_schema_upgrade_d.cpp index 0de0cb58e72..c4b3b73bad8 100644 --- a/src/mongo/db/commands/auth_schema_upgrade_d.cpp +++ b/src/mongo/db/commands/auth_schema_upgrade_d.cpp @@ -46,73 +46,71 @@ namespace { Status checkReplicaMemberVersions() { - // TODO(spencer): SERVER-14460 Resurrect or remove this function. -// if (repl::getGlobalReplicationCoordinator()->getReplicationMode() != -// repl::ReplicationCoordinator::modeReplSet) -// return Status::OK(); -// -// -// std::list<repl::Target> rsMembers; -// try { -// const unsigned rsSelfId = repl::theReplSet->selfId(); -// const std::vector<repl::ReplSetConfig::MemberCfg>& rsMemberConfigs = -// repl::theReplSet->config().members; -// for (size_t i = 0; i < rsMemberConfigs.size(); ++i) { -// const unsigned otherId = rsMemberConfigs[i]._id; -// if (rsSelfId == otherId) -// continue; -// const repl::Member* other = repl::theReplSet->findById(otherId); -// if (!other) { -// log() << "During authSchemaUpgrade, no information about replica set member " -// "with id " << otherId << "; ignoring."; -// continue; -// } -// if (!other->hbinfo().maybeUp()) { -// log() << "During authSchemaUpgrade, replica set member " << other->h() << -// " is down; ignoring."; -// continue; -// } -// rsMembers.push_back(repl::Target(other->fullName())); -// } -// -// multiCommand(BSON("buildInfo" << 1), rsMembers); -// } -// catch (const DBException& ex) { -// return ex.toStatus(); -// } -// -// for (std::list<repl::Target>::const_iterator iter = rsMembers.begin(); -// iter != rsMembers.end(); -// ++iter) { -// -// if (!iter->ok) { -// logger::LogstreamBuilder wlog = warning(); -// wlog << "During authSchemaUpgrade, could not run buildInfo command on " << -// iter->toHost; -// if (!iter->result.isEmpty()) -// wlog << "; response was " << iter->result.toString(); -// wlog << "; ignoring."; -// continue; -// } -// -// const char* version = iter->result["version"].valuestrsafe(); -// if (!*version) { -// return Status(ErrorCodes::RemoteValidationError, mongoutils::str::stream() << -// "Missing or non-string \"version\" field in result of buildInfo " -// "command sent to " << iter->toHost << "; found " << -// iter->result["version"]); -// } -// -// if (!isSameMajorVersion(version)) { -// BSONArray foundVersionArray = toVersionArray(version); -// return Status(ErrorCodes::RemoteValidationError, mongoutils::str::stream() << -// "To upgrade auth schema in a replica set, all members must be " -// "running the same release series of mongod; found " << -// foundVersionArray["0"] << '.' << foundVersionArray["1"] << -// " on host " << iter->toHost << " but expected " << -// versionArray["0"] << '.' << versionArray["1"]); -// } -// } + if (repl::getGlobalReplicationCoordinator()->getReplicationMode() != + repl::ReplicationCoordinator::modeReplSet) + return Status::OK(); + + std::list<repl::Target> rsMembers; + try { + const unsigned rsSelfId = repl::theReplSet->selfId(); + const std::vector<repl::ReplSetConfig::MemberCfg>& rsMemberConfigs = + repl::theReplSet->config().members; + for (size_t i = 0; i < rsMemberConfigs.size(); ++i) { + const unsigned otherId = rsMemberConfigs[i]._id; + if (rsSelfId == otherId) + continue; + const repl::Member* other = repl::theReplSet->findById(otherId); + if (!other) { + log() << "During authSchemaUpgrade, no information about replica set member " + "with id " << otherId << "; ignoring."; + continue; + } + if (!other->hbinfo().maybeUp()) { + log() << "During authSchemaUpgrade, replica set member " << other->h() << + " is down; ignoring."; + continue; + } + rsMembers.push_back(repl::Target(other->fullName())); + } + + multiCommand(BSON("buildInfo" << 1), rsMembers); + } + catch (const DBException& ex) { + return ex.toStatus(); + } + + for (std::list<repl::Target>::const_iterator iter = rsMembers.begin(); + iter != rsMembers.end(); + ++iter) { + + if (!iter->ok) { + logger::LogstreamBuilder wlog = warning(); + wlog << "During authSchemaUpgrade, could not run buildInfo command on " << + iter->toHost; + if (!iter->result.isEmpty()) + wlog << "; response was " << iter->result.toString(); + wlog << "; ignoring."; + continue; + } + + const char* version = iter->result["version"].valuestrsafe(); + if (!*version) { + return Status(ErrorCodes::RemoteValidationError, mongoutils::str::stream() << + "Missing or non-string \"version\" field in result of buildInfo " + "command sent to " << iter->toHost << "; found " << + iter->result["version"]); + } + + if (!isSameMajorVersion(version)) { + BSONArray foundVersionArray = toVersionArray(version); + return Status(ErrorCodes::RemoteValidationError, mongoutils::str::stream() << + "To upgrade auth schema in a replica set, all members must be " + "running the same release series of mongod; found " << + foundVersionArray["0"] << '.' << foundVersionArray["1"] << + " on host " << iter->toHost << " but expected " << + versionArray["0"] << '.' << versionArray["1"]); + } + } return Status(ErrorCodes::InternalError, "Auth schema upgrade check for replica sets not implemented"); } diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 0233adc067d..f4970409bd5 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -278,11 +278,11 @@ namespace mongo { return status; } - if (foundSchemaVersion != AuthorizationManager::schemaVersion26Final) { + if (foundSchemaVersion < AuthorizationManager::schemaVersion26Final) { return Status( ErrorCodes::AuthSchemaIncompatible, str::stream() << "User and role management commands require auth data to have " - "schema version " << AuthorizationManager::schemaVersion26Final << + "at least schema version " << AuthorizationManager::schemaVersion26Final << " but found " << foundSchemaVersion); } return authzManager->writeAuthSchemaVersionIfNeeded(txn); @@ -296,13 +296,12 @@ namespace mongo { return status; } - if (foundSchemaVersion != AuthorizationManager::schemaVersion26Final && - foundSchemaVersion != AuthorizationManager::schemaVersion26Upgrade) { + if (foundSchemaVersion < AuthorizationManager::schemaVersion26Upgrade) { return Status( ErrorCodes::AuthSchemaIncompatible, str::stream() << "The usersInfo and rolesInfo commands require auth data to " - "have schema version " << AuthorizationManager::schemaVersion26Final << - " or " << AuthorizationManager::schemaVersion26Upgrade << + "have at least schema version " << + AuthorizationManager::schemaVersion26Upgrade << " but found " << foundSchemaVersion); } return Status::OK(); @@ -427,14 +426,26 @@ namespace mongo { args.mechanism.empty()) { // At the moment we are ignoring the mechanism parameter and create - // both SCRAM-SHA-1 and MONGODB-CR credentials for all new users + // both SCRAM-SHA-1 and MONGODB-CR credentials for all new users. BSONObjBuilder credentialsBuilder(userObjBuilder.subobjStart("credentials")); - BSONObj scramCred = scram::generateCredentials(args.hashedPassword); - - if(!scramCred.isEmpty()) { - credentialsBuilder.append("SCRAM-SHA-1",scramCred); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + int authzVersion; + Status status = authzManager->getAuthorizationVersion(txn, &authzVersion); + if (!status.isOK()) { + return appendCommandStatus(result, status); + } + + // Add SCRAM credentials for appropriate authSchemaVersions. + if (authzVersion > AuthorizationManager::schemaVersion26Final) { + BSONObj scramCred = scram::generateCredentials(args.hashedPassword); + if(!scramCred.isEmpty()) { + credentialsBuilder.append("SCRAM-SHA-1", scramCred); + } + } + else { // Otherwise default to MONGODB-CR. + credentialsBuilder.append("MONGODB-CR", args.hashedPassword); } - credentialsBuilder.append("MONGODB-CR", args.hashedPassword); credentialsBuilder.done(); } else { @@ -594,11 +605,24 @@ namespace mongo { if (args.hasHashedPassword) { // Create both SCRAM-SHA-1 and MONGODB-CR credentials for all new users BSONObjBuilder credentialsBuilder(updateSetBuilder.subobjStart("credentials")); - BSONObj scramCred = scram::generateCredentials(args.hashedPassword); - if(!scramCred.isEmpty()) { - credentialsBuilder.append("SCRAM-SHA-1",scramCred); + + AuthorizationManager* authzManager = getGlobalAuthorizationManager(); + int authzVersion; + Status status = authzManager->getAuthorizationVersion(txn, &authzVersion); + if (!status.isOK()) { + return appendCommandStatus(result, status); + } + + // Add SCRAM credentials for appropriate authSchemaVersions + if (authzVersion > AuthorizationManager::schemaVersion26Final) { + BSONObj scramCred = scram::generateCredentials(args.hashedPassword); + if(!scramCred.isEmpty()) { + credentialsBuilder.append("SCRAM-SHA-1",scramCred); + } + } + else { // Otherwise default to MONGODB-CR + credentialsBuilder.append("MONGODB-CR", args.hashedPassword); } - credentialsBuilder.append("MONGODB-CR", args.hashedPassword); credentialsBuilder.done(); } if (args.hasCustomData) { diff --git a/src/mongo/db/db.cpp b/src/mongo/db/db.cpp index aeea816ba85..2152f8c6e01 100644 --- a/src/mongo/db/db.cpp +++ b/src/mongo/db/db.cpp @@ -660,7 +660,7 @@ namespace mongo { << endl; exitCleanly(EXIT_NEED_UPGRADE); } - if (foundSchemaVersion != AuthorizationManager::schemaVersion26Final) { + if (foundSchemaVersion < AuthorizationManager::schemaVersion26Final) { log() << "Auth schema version is incompatible: " << "User and role management commands require auth data to have " << "schema version " << AuthorizationManager::schemaVersion26Final diff --git a/src/mongo/s/commands/auth_schema_upgrade_s.cpp b/src/mongo/s/commands/auth_schema_upgrade_s.cpp index 747624a125a..3a9a97af747 100644 --- a/src/mongo/s/commands/auth_schema_upgrade_s.cpp +++ b/src/mongo/s/commands/auth_schema_upgrade_s.cpp @@ -152,7 +152,7 @@ namespace { Status(ErrorCodes::LockBusy, "Could not lock auth data update lock.")); } - status = checkClusterMongoVersions(configServer.getConnectionString(), "2.5.4"); + status = checkClusterMongoVersions(configServer.getConnectionString(), "2.7.6"); if (!status.isOK()) { log() << "Auth schema upgrade failed: " << status << endl; return appendCommandStatus(result, status); |