summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorVarun Ravichandran <varun.ravichandran@mongodb.com>2021-10-12 20:19:24 +0000
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2021-10-19 14:55:51 +0000
commit13d81879027e51742205433d4b3573a0c9dbeaec (patch)
tree8241148d3dcaf240c6f4c827e446c297672df9a2 /src
parent40b6c60df9c863a1f473287fcc139f71dcd2954a (diff)
downloadmongo-13d81879027e51742205433d4b3573a0c9dbeaec.tar.gz
SERVER-60425: Cache users during exact-match usersInfo commands
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/auth/SConscript1
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_s.cpp14
-rw-r--r--src/mongo/db/auth/user.cpp72
-rw-r--r--src/mongo/db/auth/user.h57
-rw-r--r--src/mongo/db/commands/user_management_commands.cpp94
-rw-r--r--src/mongo/db/commands/user_management_commands.idl6
-rw-r--r--src/mongo/util/uuid.h8
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;
}