diff options
author | Sara Golemon <sara.golemon@mongodb.com> | 2019-03-29 16:15:10 +0000 |
---|---|---|
committer | Sara Golemon <sara.golemon@mongodb.com> | 2019-06-08 16:31:24 +0000 |
commit | 64d8e9e1b12d16b54d6a592bae8110226c491b4e (patch) | |
tree | bee8bf78265cabeffd0285a19e36218e7e578d02 | |
parent | 1cb17cca8d2cff0888a83adcdd7f9ceedc237dad (diff) | |
download | mongo-64d8e9e1b12d16b54d6a592bae8110226c491b4e.tar.gz |
SERVER-38984 Validate unique User ID on UserCache hit
(cherry picked from commit e55d6e2292e5dbe2f97153251d8193d1cc89f5d7)
-rw-r--r-- | jstests/auth/deleted_recreated_user.js | 74 | ||||
-rw-r--r-- | src/mongo/base/error_codes.err | 1 | ||||
-rw-r--r-- | src/mongo/bson/bsonelement.h | 30 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.cpp | 22 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager.h | 9 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.cpp | 3 | ||||
-rw-r--r-- | src/mongo/db/auth/user.h | 13 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser.cpp | 28 | ||||
-rw-r--r-- | src/mongo/db/auth/user_document_parser.h | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 2 | ||||
-rw-r--r-- | src/mongo/util/SConscript | 10 | ||||
-rw-r--r-- | src/mongo/util/uuid.cpp | 76 | ||||
-rw-r--r-- | src/mongo/util/uuid.h | 66 |
14 files changed, 331 insertions, 5 deletions
diff --git a/jstests/auth/deleted_recreated_user.js b/jstests/auth/deleted_recreated_user.js new file mode 100644 index 00000000000..832a1defca0 --- /dev/null +++ b/jstests/auth/deleted_recreated_user.js @@ -0,0 +1,74 @@ +// Test that sessions can not be resumed by deleted and recreated user. + +(function() { + 'use strict'; + + const kInvalidationIntervalSecs = 5; + + function runTest(s0, s1) { + assert(s0); + assert(s1); + const admin = s0.getDB('admin'); + + function checkIdType(username) { + const user = admin.system.users.find({user: username, db: 'admin'}).toArray()[0]; + const id = user._id; + const userId = user.userId; + assert.eq(typeof(id), 'string'); + assert.eq(id, 'admin.' + username); + assert.eq(typeof(userId), 'object'); + assert.eq(tojson(userId).substring(0, 7), 'BinData'); + } + + admin.createUser({user: 'admin', pwd: 'pass', roles: jsTest.adminUserRoles}); + assert(admin.auth('admin', 'pass')); + checkIdType('admin'); + + admin.createUser({user: 'user', pwd: 'pass', roles: jsTest.basicUserRoles}); + checkIdType('user'); + admin.logout(); + + // Connect as basic user and create a session. + assert(admin.auth('user', 'pass')); + assert.writeOK(admin.mycoll.insert({_id: "foo", data: "bar"})); + + // Perform administrative commands via separate shell. + function evalCmd(cmd) { + const uri = 'mongodb://admin:pass@localhost:' + s1.port + '/admin'; + const result = runMongoProgram('./mongo', uri, '--eval', cmd); + assert.eq(result, 0, "Command failed"); + } + evalCmd('db.dropUser("user"); '); + evalCmd('db.createUser({user: "user", pwd: "secret", roles: ["root"]});'); + + if (s0 !== s1) { + // Wait for twice the invalidation interval when sharding. + sleep(2 * kInvalidationIntervalSecs * 1000); + } + + // This should fail due to invalid user session. + const thrown = + assert.throws(() => admin.mycoll.find({}).toArray(), [], "Able to find after recreate"); + assert.eq(thrown.code, ErrorCodes.Unauthorized, "Threw something other than unauthorized"); + } + + const mongod = MongoRunner.runMongod({auth: ''}); + runTest(mongod, mongod); + MongoRunner.stopMongod(mongod); + + // TODO: Remove 'shardAsReplicaSet: false' when SERVER-32672 is fixed. + const st = new ShardingTest({ + shards: 1, + mongos: 2, + config: 1, + other: { + keyFile: 'jstests/libs/key1', + shardAsReplicaSet: false, + mongosOptions: { + setParameter: 'userCacheInvalidationIntervalSecs=' + kInvalidationIntervalSecs, + }, + }, + }); + runTest(st.s0, st.s1); + st.stop(); +})(); diff --git a/src/mongo/base/error_codes.err b/src/mongo/base/error_codes.err index 5ace2beae56..f8557db4479 100644 --- a/src/mongo/base/error_codes.err +++ b/src/mongo/base/error_codes.err @@ -200,6 +200,7 @@ error_code("ReplicaSetMonitorRemoved", 199) error_code("ChunkRangeCleanupPending", 200) error_code("CannotBuildIndexKeys", 201) error_code("NetworkInterfaceExceededTimeLimit", 202) +error_code("InvalidUUID", 207) error_code("TooManyLocks", 208) error_code("KeyNotFound", 211) error_code("UpdateOperationFailed", 218) diff --git a/src/mongo/bson/bsonelement.h b/src/mongo/bson/bsonelement.h index 111cbad442f..f852992157f 100644 --- a/src/mongo/bson/bsonelement.h +++ b/src/mongo/bson/bsonelement.h @@ -579,6 +579,36 @@ public: return Timestamp(); } + bool isBinData(BinDataType bdt) const { + return (type() == BinData) && (binDataType() == bdt); + } + + const std::array<unsigned char, 16> uuid() const { + int len = 0; + const char* data = nullptr; + if (isBinData(BinDataType::newUUID)) { + data = binData(len); + } + uassert(ErrorCodes::InvalidUUID, + "uuid must be a 16-byte binary field with UUID (4) subtype", + len == 16); + std::array<unsigned char, 16> result; + memcpy(&result, data, len); + return result; + } + + const std::array<unsigned char, 16> md5() const { + int len = 0; + const char* data = nullptr; + if (isBinData(BinDataType::MD5Type)) { + data = binData(len); + } + uassert(40437, "md5 must be a 16-byte binary field with MD5 (5) subtype", len == 16); + std::array<unsigned char, 16> result; + memcpy(&result, data, len); + return result; + } + Date_t timestampTime() const { unsigned long long t = ConstDataView(value() + 4).read<LittleEndian<unsigned int>>(); return Date_t::fromMillisSinceEpoch(t * 1000); diff --git a/src/mongo/db/auth/authorization_manager.cpp b/src/mongo/db/auth/authorization_manager.cpp index df560952d68..4eb05ec666f 100644 --- a/src/mongo/db/auth/authorization_manager.cpp +++ b/src/mongo/db/auth/authorization_manager.cpp @@ -84,6 +84,7 @@ MONGO_INITIALIZER_WITH_PREREQUISITES(SetupInternalSecurityUser, MONGO_NO_PREREQU return Status::OK(); } +const std::string AuthorizationManager::USERID_FIELD_NAME = "userId"; const std::string AuthorizationManager::USER_NAME_FIELD_NAME = "user"; const std::string AuthorizationManager::USER_DB_FIELD_NAME = "db"; const std::string AuthorizationManager::ROLE_NAME_FIELD_NAME = "role"; @@ -394,6 +395,8 @@ Status AuthorizationManager::_initializeUserFromPrivilegeDocument(User* user, 0); } + user->setID(parser.extractUserIDFromUserDocument(privDoc)); + Status status = parser.initializeUserCredentialsFromUserDocument(user, privDoc); if (!status.isOK()) { return status; @@ -535,6 +538,25 @@ Status AuthorizationManager::acquireUser(OperationContext* txn, return Status::OK(); } +Status AuthorizationManager::acquireUserForSessionRefresh(OperationContext* opCtx, + const UserName& userName, + const User::UserId& uid, + User** user) { + auto status = acquireUser(opCtx, userName, user); + if (!status.isOK()) { + return status; + } + + if (uid != (*user)->getID()) { + *user = nullptr; + return {ErrorCodes::UserNotFound, + str::stream() << "User id from privilege document '" << userName.toString() + << "' does not match user id in session."}; + } + + return Status::OK(); +} + Status AuthorizationManager::_fetchUserV2(OperationContext* txn, const UserName& userName, std::unique_ptr<User>* acquiredUser) { diff --git a/src/mongo/db/auth/authorization_manager.h b/src/mongo/db/auth/authorization_manager.h index b12abccad40..0fe7833f70c 100644 --- a/src/mongo/db/auth/authorization_manager.h +++ b/src/mongo/db/auth/authorization_manager.h @@ -81,6 +81,7 @@ public: ~AuthorizationManager(); + static const std::string USERID_FIELD_NAME; static const std::string USER_NAME_FIELD_NAME; static const std::string USER_DB_FIELD_NAME; static const std::string ROLE_NAME_FIELD_NAME; @@ -262,6 +263,14 @@ public: Status acquireUser(OperationContext* txn, const UserName& userName, User** acquiredUser); /** + * Validate the ID associated with a known user while refreshing session cache. + */ + Status acquireUserForSessionRefresh(OperationContext* opCtx, + const UserName& userName, + const User::UserId& uid, + User** acquiredUser); + + /** * Decrements the refcount of the given User object. If the refcount has gone to zero, * deletes the User. Caller must stop using its pointer to "user" after calling this. */ diff --git a/src/mongo/db/auth/authorization_session.cpp b/src/mongo/db/auth/authorization_session.cpp index ba923ecf711..1c967fc59bf 100644 --- a/src/mongo/db/auth/authorization_session.cpp +++ b/src/mongo/db/auth/authorization_session.cpp @@ -760,7 +760,8 @@ void AuthorizationSession::_refreshUserInfoAsNeeded(OperationContext* txn) { UserName name = user->getName(); User* updatedUser; - Status status = authMan.acquireUser(txn, name, &updatedUser); + Status status = + authMan.acquireUserForSessionRefresh(txn, name, user->getID(), &updatedUser); stdx::lock_guard<Client> lk(*txn->getClient()); switch (status.code()) { diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index cdef6e52ddd..5b0563395e3 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -91,6 +91,15 @@ public: explicit User(const UserName& name); ~User(); + using UserId = std::vector<std::uint8_t>; + const UserId& getID() const { + return _id; + } + + void setID(UserId id) { + _id = std::move(id); + } + /** * Returns the user name for this user. */ @@ -215,6 +224,10 @@ public: void decrementRefCount(); private: + // Unique ID (often UUID) for this user. + // May be empty for legacy users. + UserId _id; + UserName _name; // Maps resource name to privilege on that resource diff --git a/src/mongo/db/auth/user_document_parser.cpp b/src/mongo/db/auth/user_document_parser.cpp index 234f3b64d73..cb320a8ff25 100644 --- a/src/mongo/db/auth/user_document_parser.cpp +++ b/src/mongo/db/auth/user_document_parser.cpp @@ -222,10 +222,18 @@ Status _checkV2RolesArray(const BSONElement& rolesElement) { } Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const { - BSONElement userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME]; - BSONElement userDBElement = doc[AuthorizationManager::USER_DB_FIELD_NAME]; - BSONElement credentialsElement = doc[CREDENTIALS_FIELD_NAME]; - BSONElement rolesElement = doc[ROLES_FIELD_NAME]; + auto userIdElement = doc[AuthorizationManager::USERID_FIELD_NAME]; + auto userElement = doc[AuthorizationManager::USER_NAME_FIELD_NAME]; + auto userDBElement = doc[AuthorizationManager::USER_DB_FIELD_NAME]; + auto credentialsElement = doc[CREDENTIALS_FIELD_NAME]; + auto rolesElement = doc[ROLES_FIELD_NAME]; + + // Validate the "userId" element. + if (!userIdElement.eoo()) { + if (!userIdElement.isBinData(BinDataType::newUUID)) { + return _badValue("User document needs 'userId' field to be a UUID", 0); + } + } // Validate the "user" element. if (userElement.type() != String) @@ -296,6 +304,18 @@ Status V2UserDocumentParser::checkValidUserDocument(const BSONObj& doc) const { return Status::OK(); } +User::UserId V2UserDocumentParser::extractUserIDFromUserDocument(const BSONObj& doc) const { + auto userId = doc[AuthorizationManager::USERID_FIELD_NAME]; + if (userId.isBinData(BinDataType::newUUID)) { + auto id = userId.uuid(); + User::UserId ret; + std::copy(id.begin(), id.end(), std::back_inserter(ret)); + return ret; + } + + return User::UserId(); +} + std::string V2UserDocumentParser::extractUserNameFromUserDocument(const BSONObj& doc) const { return doc[AuthorizationManager::USER_NAME_FIELD_NAME].str(); } diff --git a/src/mongo/db/auth/user_document_parser.h b/src/mongo/db/auth/user_document_parser.h index 502a3f349ea..15b913bcfea 100644 --- a/src/mongo/db/auth/user_document_parser.h +++ b/src/mongo/db/auth/user_document_parser.h @@ -67,6 +67,7 @@ public: static Status parseRoleVector(const BSONArray& rolesArray, std::vector<RoleName>* result); std::string extractUserNameFromUserDocument(const BSONObj& doc) const; + User::UserId extractUserIDFromUserDocument(const BSONObj& doc) const; Status initializeUserCredentialsFromUserDocument(User* user, const BSONObj& privDoc) const; diff --git a/src/mongo/db/commands/SConscript b/src/mongo/db/commands/SConscript index 6a394e54e97..f6f08e42187 100644 --- a/src/mongo/db/commands/SConscript +++ b/src/mongo/db/commands/SConscript @@ -153,6 +153,7 @@ env.Library( '$BUILD_DIR/mongo/db/stats/serveronly', '$BUILD_DIR/mongo/db/storage/mmap_v1/storage_mmapv1', '$BUILD_DIR/mongo/s/client/parallel', + '$BUILD_DIR/mongo/util/uuid', 'apply_ops_cmd_common', 'core', 'killcursors_common', diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 061598fb4b6..b56cc8a68fe 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -74,6 +74,7 @@ #include "mongo/util/net/ssl_manager.h" #include "mongo/util/sequence_util.h" #include "mongo/util/time_support.h" +#include "mongo/util/uuid.h" namespace mongo { @@ -667,6 +668,7 @@ public: BSONObjBuilder userObjBuilder; userObjBuilder.append( "_id", str::stream() << args.userName.getDB() << "." << args.userName.getUser()); + UUID::gen().appendToBuilder(&userObjBuilder, AuthorizationManager::USERID_FIELD_NAME); userObjBuilder.append(AuthorizationManager::USER_NAME_FIELD_NAME, args.userName.getUser()); userObjBuilder.append(AuthorizationManager::USER_DB_FIELD_NAME, args.userName.getDB()); diff --git a/src/mongo/util/SConscript b/src/mongo/util/SConscript index 7918ca945ac..96f393af30d 100644 --- a/src/mongo/util/SConscript +++ b/src/mongo/util/SConscript @@ -129,6 +129,16 @@ env.Library( ], ) +env.Library( + target='uuid', + source=[ + 'uuid.cpp', + ], + LIBDEPS=[ + '$BUILD_DIR/mongo/base', + ], +) + env.CppUnitTest( target="md5_test", source=[ diff --git a/src/mongo/util/uuid.cpp b/src/mongo/util/uuid.cpp new file mode 100644 index 00000000000..5594a7468a0 --- /dev/null +++ b/src/mongo/util/uuid.cpp @@ -0,0 +1,76 @@ +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side 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. + */ + +#include "mongo/platform/basic.h" + +#include "mongo/util/uuid.h" + +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/bsontypes.h" +#include "mongo/platform/random.h" +#include "mongo/stdx/mutex.h" + +namespace mongo { + +namespace { + +stdx::mutex uuidGenMutex; +auto uuidGen = SecureRandom::create(); + +} // namespace + +UUID UUID::gen() { + int64_t randomWords[2]; + + { + stdx::lock_guard<stdx::mutex> lk(uuidGenMutex); + + // Generate 128 random bits + randomWords[0] = uuidGen->nextInt64(); + randomWords[1] = uuidGen->nextInt64(); + } + + UUIDStorage randomBytes; + memcpy(&randomBytes, randomWords, sizeof(randomBytes)); + + // Set version in high 4 bits of byte 6 and variant in high 2 bits of byte 8, see RFC 4122, + // section 4.1.1, 4.1.2 and 4.1.3. + randomBytes[6] &= 0x0f; + randomBytes[6] |= 0x40; // v4 + randomBytes[8] &= 0x3f; + randomBytes[8] |= 0x80; // Randomly assigned + + return UUID{randomBytes}; +} + +void UUID::appendToBuilder(BSONObjBuilder* builder, StringData name) const { + builder->appendBinData(name, sizeof(UUIDStorage), BinDataType::newUUID, &_uuid); +} + +} // namespace mongo diff --git a/src/mongo/util/uuid.h b/src/mongo/util/uuid.h new file mode 100644 index 00000000000..aeee32dcf50 --- /dev/null +++ b/src/mongo/util/uuid.h @@ -0,0 +1,66 @@ + +/** + * Copyright (C) 2018-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * 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 + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * <http://www.mongodb.com/licensing/server-side-public-license>. + * + * 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 Server Side 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. + */ + +#pragma once + +#include <array> + +#include "mongo/base/string_data.h" +#include "mongo/bson/bsonobjbuilder.h" + +namespace mongo { + +/** + * A UUID is a 128-bit unique identifier, per RFC 4122, v4, using + * a secure random number generator. + */ +class UUID { + using UUIDStorage = std::array<unsigned char, 16>; + +public: + /** + * Generate a new random v4 UUID per RFC 4122. + */ + static UUID gen(); + + /** + * Appends to builder as BinData(4, "...") element with the given name. + */ + void appendToBuilder(BSONObjBuilder* builder, StringData name) const; + +private: + UUID(const UUIDStorage& uuid) : _uuid(uuid) {} + + UUID() = default; + + UUIDStorage _uuid{}; // UUID in network byte order +}; + +} // namespace mongo |