summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSara Golemon <sara.golemon@mongodb.com>2019-03-29 16:15:10 +0000
committerSara Golemon <sara.golemon@mongodb.com>2019-06-08 16:31:24 +0000
commit64d8e9e1b12d16b54d6a592bae8110226c491b4e (patch)
treebee8bf78265cabeffd0285a19e36218e7e578d02
parent1cb17cca8d2cff0888a83adcdd7f9ceedc237dad (diff)
downloadmongo-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.js74
-rw-r--r--src/mongo/base/error_codes.err1
-rw-r--r--src/mongo/bson/bsonelement.h30
-rw-r--r--src/mongo/db/auth/authorization_manager.cpp22
-rw-r--r--src/mongo/db/auth/authorization_manager.h9
-rw-r--r--src/mongo/db/auth/authorization_session.cpp3
-rw-r--r--src/mongo/db/auth/user.h13
-rw-r--r--src/mongo/db/auth/user_document_parser.cpp28
-rw-r--r--src/mongo/db/auth/user_document_parser.h1
-rw-r--r--src/mongo/db/commands/SConscript1
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp2
-rw-r--r--src/mongo/util/SConscript10
-rw-r--r--src/mongo/util/uuid.cpp76
-rw-r--r--src/mongo/util/uuid.h66
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