diff options
author | Varun Ravichandran <varun.ravichandran@mongodb.com> | 2021-10-12 20:19:24 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-10-19 14:55:51 +0000 |
commit | 13d81879027e51742205433d4b3573a0c9dbeaec (patch) | |
tree | 8241148d3dcaf240c6f4c827e446c297672df9a2 /src | |
parent | 40b6c60df9c863a1f473287fcc139f71dcd2954a (diff) | |
download | mongo-13d81879027e51742205433d4b3573a0c9dbeaec.tar.gz |
SERVER-60425: Cache users during exact-match usersInfo commands
Diffstat (limited to 'src')
-rw-r--r-- | src/mongo/db/auth/SConscript | 1 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.cpp | 14 | ||||
-rw-r--r-- | src/mongo/db/auth/user.cpp | 72 | ||||
-rw-r--r-- | src/mongo/db/auth/user.h | 57 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.cpp | 94 | ||||
-rw-r--r-- | src/mongo/db/commands/user_management_commands.idl | 6 | ||||
-rw-r--r-- | src/mongo/util/uuid.h | 8 |
7 files changed, 209 insertions, 43 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index c99651c652e..9af94b20d27 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -98,6 +98,7 @@ env.Library( ], LIBDEPS_PRIVATE=[ '$BUILD_DIR/mongo/crypto/sha_block_${MONGO_CRYPTO}', + 'auth', 'authentication_restriction', 'authprivilege', ], diff --git a/src/mongo/db/auth/authz_manager_external_state_s.cpp b/src/mongo/db/auth/authz_manager_external_state_s.cpp index 1b2c0b7331c..47cf83499bc 100644 --- a/src/mongo/db/auth/authz_manager_external_state_s.cpp +++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp @@ -132,13 +132,13 @@ Status AuthzManagerExternalStateMongos::getUserDescription(OperationContext* opC BSONObj* result) { const UserName& userName = user.name; if (!user.roles) { - 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 - << "showAuthenticationRestrictions" << true); + 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 + << "showAuthenticationRestrictions" << true << "showCustomData" << false); BSONObjBuilder builder; const bool ok = Grid::get(opCtx)->catalogClient()->runUserManagementReadCommand( opCtx, "admin", usersInfoCmd, &builder); diff --git a/src/mongo/db/auth/user.cpp b/src/mongo/db/auth/user.cpp index ff967a9f8a0..6b6cf35d259 100644 --- a/src/mongo/db/auth/user.cpp +++ b/src/mongo/db/auth/user.cpp @@ -45,6 +45,19 @@ namespace mongo { namespace { +// Field names used when serializing User objects to BSON for usersInfo. +constexpr auto kIdFieldName = "_id"_sd; +constexpr auto kUserIdFieldName = "userId"_sd; +constexpr auto kUserFieldName = "user"_sd; +constexpr auto kDbFieldName = "db"_sd; +constexpr auto kMechanismsFieldName = "mechanisms"_sd; +constexpr auto kCredentialsFieldName = "credentials"_sd; +constexpr auto kRolesFieldName = "roles"_sd; +constexpr auto kInheritedRolesFieldName = "inheritedRoles"_sd; +constexpr auto kInheritedPrivilegesFieldName = "inheritedPrivileges"_sd; +constexpr auto kInheritedAuthenticationRestrictionsFieldName = + "inheritedAuthenticationRestrictions"_sd; +constexpr auto kAuthenticationRestrictionsFieldName = "authenticationRestrictions"_sd; SHA256Block computeDigest(const UserName& name) { auto fn = name.getDisplayName(); @@ -181,4 +194,63 @@ Status User::validateRestrictions(OperationContext* opCtx) const { return Status::OK(); } +void User::reportForUsersInfo(BSONObjBuilder* builder, + bool showCredentials, + bool showPrivileges, + bool showAuthenticationRestrictions) const { + builder->append(kIdFieldName, _name.getUnambiguousName()); + UUID::fromCDR(ConstDataRange(_id)).appendToBuilder(builder, kUserIdFieldName); + builder->append(kUserFieldName, _name.getUser()); + builder->append(kDbFieldName, _name.getDB()); + + BSONArrayBuilder mechanismNamesBuilder(builder->subarrayStart(kMechanismsFieldName)); + for (const StringData& mechanism : _credentials.toMechanismsVector()) { + mechanismNamesBuilder.append(mechanism); + } + mechanismNamesBuilder.doneFast(); + + BSONArrayBuilder rolesBuilder(builder->subarrayStart(kRolesFieldName)); + for (const auto& role : _roles) { + role.serializeToBSON(&rolesBuilder); + } + rolesBuilder.doneFast(); + + if (showCredentials) { + BSONObjBuilder credentialsBuilder(builder->subobjStart(kCredentialsFieldName)); + _credentials.toBSON(&credentialsBuilder); + credentialsBuilder.doneFast(); + } + + if (showPrivileges || showAuthenticationRestrictions) { + BSONArrayBuilder inheritedRolesBuilder(builder->subarrayStart(kInheritedRolesFieldName)); + for (const auto& indirectRole : _indirectRoles) { + indirectRole.serializeToBSON(&inheritedRolesBuilder); + } + inheritedRolesBuilder.doneFast(); + + BSONArrayBuilder privsBuilder(builder->subarrayStart(kInheritedPrivilegesFieldName)); + for (const auto& resourceToPrivilege : _privileges) { + privsBuilder.append(resourceToPrivilege.second.toBSON()); + } + privsBuilder.doneFast(); + + BSONArray indirectRestrictionsArr = _indirectRestrictions.toBSON(); + builder->append(kInheritedAuthenticationRestrictionsFieldName, indirectRestrictionsArr); + } + + if (showAuthenticationRestrictions) { + // The user document parser expects an array of documents, where each document represents + // a restriction. Since _restrictions is of type RestrictionDocuments, its serialization + // logic supports multiple arrays of documents rather than just one. Therefore, we only + // should append the first array here. + BSONArray authenticationRestrictionsArr = _restrictions.toBSON(); + if (authenticationRestrictionsArr.nFields() == 0) { + builder->append(kAuthenticationRestrictionsFieldName, BSONArray()); + } else { + builder->append(kAuthenticationRestrictionsFieldName, + BSONArray(authenticationRestrictionsArr.begin()->Obj())); + } + } +} + } // namespace mongo diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h index 4a6b204ca76..4a1045ad4d7 100644 --- a/src/mongo/db/auth/user.h +++ b/src/mongo/db/auth/user.h @@ -66,6 +66,13 @@ class User { public: using UserId = std::vector<std::uint8_t>; + constexpr static auto kSHA1FieldName = "SCRAM-SHA-1"_sd; + constexpr static auto kSHA256FieldName = "SCRAM-SHA-256"_sd; + constexpr static auto kExternalFieldName = "external"_sd; + constexpr static auto kIterationCountFieldName = "iterationCount"_sd; + constexpr static auto kSaltFieldName = "salt"_sd; + constexpr static auto kServerKeyFieldName = "serverKey"_sd; + constexpr static auto kStoredKeyFieldName = "storedKey"_sd; template <typename HashBlock> struct SCRAMCredentials { @@ -89,6 +96,13 @@ public: bool empty() const { return !iterationCount && salt.empty() && serverKey.empty() && storedKey.empty(); } + + void toBSON(BSONObjBuilder* builder) const { + builder->append(kIterationCountFieldName, iterationCount); + builder->append(kSaltFieldName, salt); + builder->append(kStoredKeyFieldName, storedKey); + builder->append(kServerKeyFieldName, serverKey); + } }; struct CredentialData { @@ -106,6 +120,40 @@ public: template <typename HashBlock> const SCRAMCredentials<HashBlock>& scram() const; + + void toBSON(BSONObjBuilder* builder) const { + if (scram_sha1.isValid()) { + BSONObjBuilder sha1ObjBuilder(builder->subobjStart(kSHA1FieldName)); + scram_sha1.toBSON(&sha1ObjBuilder); + sha1ObjBuilder.doneFast(); + } + if (scram_sha256.isValid()) { + BSONObjBuilder sha256ObjBuilder(builder->subobjStart(kSHA256FieldName)); + scram_sha256.toBSON(&sha256ObjBuilder); + sha256ObjBuilder.doneFast(); + } + if (isExternal) { + builder->append(kExternalFieldName, true); + } + } + + std::vector<StringData> toMechanismsVector() const { + std::vector<StringData> mechanismsVec; + if (scram_sha1.isValid()) { + mechanismsVec.push_back(kSHA1FieldName); + } + if (scram_sha256.isValid()) { + mechanismsVec.push_back(kSHA256FieldName); + } + if (isExternal) { + mechanismsVec.push_back(kExternalFieldName); + } + + // Valid CredentialData objects must have at least one mechanism. + invariant(mechanismsVec.size() > 0); + + return mechanismsVec; + } }; using ResourcePrivilegeMap = stdx::unordered_map<ResourcePattern, Privilege>; @@ -245,6 +293,15 @@ public: */ Status validateRestrictions(OperationContext* opCtx) const; + /** + * Generates a BSON representation of the User object with all the information needed for + * usersInfo. + */ + void reportForUsersInfo(BSONObjBuilder* builder, + bool showCredentials, + bool showPrivileges, + bool showAuthenticationRestrictions) const; + private: // Unique ID (often UUID) for this user. May be empty for legacy users. UserId _id; diff --git a/src/mongo/db/commands/user_management_commands.cpp b/src/mongo/db/commands/user_management_commands.cpp index 28c1590e8d5..8944e3cdb52 100644 --- a/src/mongo/db/commands/user_management_commands.cpp +++ b/src/mongo/db/commands/user_management_commands.cpp @@ -1343,41 +1343,64 @@ UsersInfoReply CmdUMCTyped<UsersInfoCommand, UMCInfoParams>::Invocation::typedRu "Privilege or restriction details require exact-match usersInfo queries", !cmd.getFilter() && arg.isExact()); - // If you want privileges or restrictions you need to call getUserDescription - // on each user. + // Exact-match usersInfo queries can be optimized to utilize the user cache if custom data + // can be omitted. This is especially helpful when config servers execute exact-match + // usersInfo queries on behalf of mongoses gathering roles + privileges for recently + // authenticated users. for (const auto& userName : arg.getElements(dbname)) { - BSONObj userDetails; - auto status = authzManager->getUserDescription(opCtx, userName, &userDetails); - if (status.code() == ErrorCodes::UserNotFound) { - continue; - } - uassertStatusOK(status); - - // getUserDescription always includes credentials and restrictions, which may need - // to be stripped out - BSONObjBuilder strippedUser; - for (const BSONElement& e : userDetails) { - if (e.fieldNameStringData() == "credentials") { - BSONArrayBuilder mechanismNamesBuilder; - BSONObj mechanismsObj = e.Obj(); - for (const BSONElement& mechanismElement : mechanismsObj) { - mechanismNamesBuilder.append(mechanismElement.fieldNameStringData()); + if (cmd.getShowCustomData()) { + BSONObj userDetails; + auto status = authzManager->getUserDescription(opCtx, userName, &userDetails); + if (status.code() == ErrorCodes::UserNotFound) { + continue; + } + uassertStatusOK(status); + + // getUserDescription always includes credentials and restrictions, which may need + // to be stripped out + BSONObjBuilder strippedUser; + for (const BSONElement& e : userDetails) { + if (e.fieldNameStringData() == "credentials") { + BSONArrayBuilder mechanismNamesBuilder; + BSONObj mechanismsObj = e.Obj(); + for (const BSONElement& mechanismElement : mechanismsObj) { + mechanismNamesBuilder.append(mechanismElement.fieldNameStringData()); + } + strippedUser.append("mechanisms", mechanismNamesBuilder.arr()); + + if (!cmd.getShowCredentials()) { + continue; + } } - strippedUser.append("mechanisms", mechanismNamesBuilder.arr()); - if (!cmd.getShowCredentials()) { + if ((e.fieldNameStringData() == "authenticationRestrictions") && + !cmd.getShowAuthenticationRestrictions()) { continue; } - } - if ((e.fieldNameStringData() == "authenticationRestrictions") && - !cmd.getShowAuthenticationRestrictions()) { + strippedUser.append(e); + } + users.push_back(strippedUser.obj()); + } else { + // Custom data is not required in the output, so it can be generated from a cached + // user object. + auto swUserHandle = authzManager->acquireUser(opCtx, userName); + if (swUserHandle.getStatus().code() == ErrorCodes::UserNotFound) { continue; } - - strippedUser.append(e); + UserHandle user = uassertStatusOK(swUserHandle); + + // The returned User object will need to be marshalled back into a BSON document and + // stripped of credentials and restrictions if they were not explicitly requested. + BSONObjBuilder userObjBuilder; + user->reportForUsersInfo(&userObjBuilder, + cmd.getShowCredentials(), + cmd.getShowPrivileges(), + cmd.getShowAuthenticationRestrictions()); + BSONObj userObj = userObjBuilder.obj(); + users.push_back(userObj); + userObjBuilder.doneFast(); } - users.push_back(strippedUser.obj()); } } else { // If you don't need privileges, or authenticationRestrictions, you can just do a @@ -1411,15 +1434,18 @@ UsersInfoReply CmdUMCTyped<UsersInfoCommand, UMCInfoParams>::Invocation::typedRu << "in" << "$$cred.k"))))); - if (cmd.getShowCredentials()) { - // Authentication restrictions are only rendered in the single user case. - pipeline.push_back(BSON("$unset" - << "authenticationRestrictions")); - } else { - // Remove credentials as well, they're not required in the output - pipeline.push_back(BSON("$unset" << BSON_ARRAY("authenticationRestrictions" - << "credentials"))); + // Authentication restrictions are only rendered in the single user case. + BSONArrayBuilder fieldsToRemoveBuilder; + fieldsToRemoveBuilder.append("authenticationRestrictions"); + if (!cmd.getShowCredentials()) { + // Remove credentials as well, they're not required in the output. + fieldsToRemoveBuilder.append("credentials"); + } + if (!cmd.getShowCustomData()) { + // Remove customData as well, it's not required in the output. + fieldsToRemoveBuilder.append("customData"); } + pipeline.push_back(BSON("$unset" << fieldsToRemoveBuilder.arr())); // Handle a user specified filter. if (auto filter = cmd.getFilter()) { diff --git a/src/mongo/db/commands/user_management_commands.idl b/src/mongo/db/commands/user_management_commands.idl index d7fa402f193..86911ba5528 100644 --- a/src/mongo/db/commands/user_management_commands.idl +++ b/src/mongo/db/commands/user_management_commands.idl @@ -358,6 +358,12 @@ commands: If viewing all users, you cannot specify this field. type: safeBool default: false + showCustomData: + description: >- + Set the field to false to omit the custom data provided by the client for the + user. + type: safeBool + default: true filter: description: >- A document that specifies $match stage conditions to return information diff --git a/src/mongo/util/uuid.h b/src/mongo/util/uuid.h index eade27480d6..078f658d07c 100644 --- a/src/mongo/util/uuid.h +++ b/src/mongo/util/uuid.h @@ -81,8 +81,12 @@ public: static UUID fromCDR(ConstDataRange cdr) { UUID uuid{UUIDStorage{}}; - invariant(cdr.length() == uuid._uuid.size()); - memcpy(uuid._uuid.data(), cdr.data(), uuid._uuid.size()); + // Allow empty CDRs to generate empty UUIDs. + if (cdr.length() > 0) { + invariant(cdr.length() == uuid._uuid.size()); + memcpy(uuid._uuid.data(), cdr.data(), uuid._uuid.size()); + } + return uuid; } |