summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorSpencer Jackson <spencer.jackson@mongodb.com>2020-02-28 19:45:13 -0500
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-03-03 00:32:53 +0000
commita1f6d2eb74268b140929c3f280c37299617d596a (patch)
tree55df6bd729ca2c663c7c75cd878bfb05d5fd732e /src
parentb7dfdde9d038433cf34e77618d4b1b114d3f4d91 (diff)
downloadmongo-a1f6d2eb74268b140929c3f280c37299617d596a.tar.gz
SERVER-46498 Store connection specified roles in authorization cache key
Diffstat (limited to 'src')
-rw-r--r--src/mongo/db/auth/authorization_manager_impl.cpp62
-rw-r--r--src/mongo/db/auth/authorization_manager_impl.h2
-rw-r--r--src/mongo/db/auth/authorization_manager_test.cpp4
-rw-r--r--src/mongo/db/auth/authz_manager_external_state.cpp21
-rw-r--r--src/mongo/db/auth/authz_manager_external_state.h24
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_local.cpp8
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_local.h2
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_s.cpp8
-rw-r--r--src/mongo/db/auth/authz_manager_external_state_s.h2
-rw-r--r--src/mongo/db/auth/user.h32
10 files changed, 107 insertions, 58 deletions
diff --git a/src/mongo/db/auth/authorization_manager_impl.cpp b/src/mongo/db/auth/authorization_manager_impl.cpp
index 174b3531215..dca8549d47c 100644
--- a/src/mongo/db/auth/authorization_manager_impl.cpp
+++ b/src/mongo/db/auth/authorization_manager_impl.cpp
@@ -41,8 +41,10 @@
#include "mongo/base/shim.h"
#include "mongo/base/status.h"
#include "mongo/bson/util/bson_extract.h"
+#include "mongo/config.h"
#include "mongo/crypto/mechanism_scram.h"
#include "mongo/db/auth/address_restriction.h"
+#include "mongo/db/auth/authorization_manager_global_parameters_gen.h"
#include "mongo/db/auth/authorization_manager_impl_parameters_gen.h"
#include "mongo/db/auth/authorization_session_impl.h"
#include "mongo/db/auth/authz_manager_external_state.h"
@@ -53,6 +55,7 @@
#include "mongo/db/mongod_options.h"
#include "mongo/logv2/log.h"
#include "mongo/util/assert_util.h"
+#include "mongo/util/net/ssl_types.h"
#include "mongo/util/str.h"
namespace mongo {
@@ -284,6 +287,29 @@ Status initializeUserFromPrivilegeDocument(User* user, const BSONObj& privDoc) {
return Status::OK();
}
+/**
+ * Returns true if roles for this user were provided by the client, and can be obtained from
+ * the connection.
+ */
+bool shouldUseRolesFromConnection(OperationContext* opCtx, const UserName& userName) {
+#ifdef MONGO_CONFIG_SSL
+ if (!opCtx || !opCtx->getClient() || !opCtx->getClient()->session()) {
+ return false;
+ }
+
+ if (!allowRolesFromX509Certificates) {
+ return false;
+ }
+
+ auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
+ return sslPeerInfo.subjectName.toString() == userName.getUser() &&
+ userName.getDB() == "$external"_sd && !sslPeerInfo.roles.empty();
+#else
+ return false;
+#endif
+}
+
+
std::unique_ptr<AuthorizationManager> authorizationManagerCreateImpl(
ServiceContext* serviceContext) {
return std::make_unique<AuthorizationManagerImpl>(serviceContext,
@@ -386,7 +412,7 @@ bool AuthorizationManagerImpl::hasAnyPrivilegeDocuments(OperationContext* opCtx)
Status AuthorizationManagerImpl::getUserDescription(OperationContext* opCtx,
const UserName& userName,
BSONObj* result) {
- return _externalState->getUserDescription(opCtx, userName, result);
+ return _externalState->getUserDescription(opCtx, UserRequest(userName, boost::none), result);
}
Status AuthorizationManagerImpl::getRoleDescription(OperationContext* opCtx,
@@ -423,7 +449,22 @@ StatusWith<UserHandle> AuthorizationManagerImpl::acquireUser(OperationContext* o
return internalSecurity.user;
}
- auto cachedUser = _userCache.acquire(opCtx, userName);
+ UserRequest request(userName, boost::none);
+
+ // Clients connected via TLS may present an X.509 certificate which contains an authorization
+ // grant. If this is the case, the roles must be provided to the external state, for expansion
+ // into privileges.
+ if (shouldUseRolesFromConnection(opCtx, userName)) {
+ auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
+ request.roles = std::set<RoleName>();
+
+ // In order to be hashable, the role names must be converted from unordered_set to a set.
+ std::copy(sslPeerInfo.roles.begin(),
+ sslPeerInfo.roles.end(),
+ std::inserter(*request.roles, request.roles->begin()));
+ }
+
+ auto cachedUser = _userCache.acquire(opCtx, request);
invariant(cachedUser);
LOGV2_DEBUG(20226, 1, "Returning user {userName} from cache", "userName"_attr = userName);
@@ -561,14 +602,17 @@ void AuthorizationManagerImpl::invalidateUserByName(OperationContext* opCtx,
LOGV2_DEBUG(20235, 2, "Invalidating user {userName}", "userName"_attr = userName);
_updateCacheGeneration();
_authSchemaVersionCache.invalidateAll();
- _userCache.invalidate(userName);
+ // Invalidate the named User, assuming no externally provided roles. When roles are defined
+ // externally, there exists no user document which may become invalid.
+ _userCache.invalidate(UserRequest(userName, boost::none));
}
void AuthorizationManagerImpl::invalidateUsersFromDB(OperationContext* opCtx, StringData dbname) {
LOGV2_DEBUG(20236, 2, "Invalidating all users from database {dbname}", "dbname"_attr = dbname);
_updateCacheGeneration();
_authSchemaVersionCache.invalidateAll();
- _userCache.invalidateIf([&](const UserName& user) { return user.getDB() == dbname; });
+ _userCache.invalidateIf(
+ [&](const UserRequest& userRequest) { return userRequest.name.getDB() == dbname; });
}
void AuthorizationManagerImpl::invalidateUserCache(OperationContext* opCtx) {
@@ -605,7 +649,7 @@ std::vector<AuthorizationManager::CachedUserInfo> AuthorizationManagerImpl::getU
ret.reserve(cacheData.size());
std::transform(
cacheData.begin(), cacheData.end(), std::back_inserter(ret), [](const auto& info) {
- return AuthorizationManager::CachedUserInfo{info.key, info.useCount > 0};
+ return AuthorizationManager::CachedUserInfo{info.key.name, info.useCount > 0};
});
return ret;
@@ -639,8 +683,8 @@ AuthorizationManagerImpl::UserCacheImpl::UserCacheImpl(
_externalState(externalState) {}
boost::optional<User> AuthorizationManagerImpl::UserCacheImpl::lookup(OperationContext* opCtx,
- const UserName& userName) {
- LOGV2_DEBUG(20238, 1, "Getting user {userName} from disk", "userName"_attr = userName);
+ const UserRequest& userReq) {
+ LOGV2_DEBUG(20238, 1, "Getting user record", "userName"_attr = userReq.name);
// Number of times to retry a user document that fetches due to transient AuthSchemaIncompatible
// errors. These errors should only ever occur during and shortly after schema upgrades.
@@ -658,9 +702,9 @@ boost::optional<User> AuthorizationManagerImpl::UserCacheImpl::lookup(OperationC
case schemaVersion26Final:
case schemaVersion26Upgrade: {
BSONObj userObj;
- uassertStatusOK(_externalState->getUserDescription(opCtx, userName, &userObj));
+ uassertStatusOK(_externalState->getUserDescription(opCtx, userReq, &userObj));
- User user(userName);
+ User user(userReq.name);
uassertStatusOK(initializeUserFromPrivilegeDocument(&user, userObj));
return user;
}
diff --git a/src/mongo/db/auth/authorization_manager_impl.h b/src/mongo/db/auth/authorization_manager_impl.h
index 88a62210097..b6e59a069ab 100644
--- a/src/mongo/db/auth/authorization_manager_impl.h
+++ b/src/mongo/db/auth/authorization_manager_impl.h
@@ -184,7 +184,7 @@ private:
// Even though the dist cache permits for lookup to return boost::none for non-existent
// values, the contract of the authorization manager is that it should throw an exception if
// the value can not be loaded, so if it returns, the value will always be set.
- boost::optional<User> lookup(OperationContext* opCtx, const UserName& userName) override;
+ boost::optional<User> lookup(OperationContext* opCtx, const UserRequest& user) override;
private:
Mutex _mutex = MONGO_MAKE_LATCH("AuthorizationManagerImpl::UserDistCacheImpl::_mutex");
diff --git a/src/mongo/db/auth/authorization_manager_test.cpp b/src/mongo/db/auth/authorization_manager_test.cpp
index f381d76ae47..944e809d1b3 100644
--- a/src/mongo/db/auth/authorization_manager_test.cpp
+++ b/src/mongo/db/auth/authorization_manager_test.cpp
@@ -234,9 +234,9 @@ public:
* can control exactly what privileges are returned for the user.
*/
Status getUserDescription(OperationContext* opCtx,
- const UserName& userName,
+ const UserRequest& user,
BSONObj* result) override {
- return _getUserDocument(opCtx, userName, result);
+ return _getUserDocument(opCtx, user.name, result);
}
private:
diff --git a/src/mongo/db/auth/authz_manager_external_state.cpp b/src/mongo/db/auth/authz_manager_external_state.cpp
index bc129430819..8384fa2b924 100644
--- a/src/mongo/db/auth/authz_manager_external_state.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state.cpp
@@ -31,7 +31,6 @@
#include "mongo/base/shim.h"
#include "mongo/config.h"
-#include "mongo/db/auth/authorization_manager_global_parameters_gen.h"
#include "mongo/db/auth/authz_manager_external_state.h"
#include "mongo/db/auth/user_name.h"
#include "mongo/db/operation_context.h"
@@ -47,24 +46,4 @@ std::unique_ptr<AuthzManagerExternalState> AuthzManagerExternalState::create() {
AuthzManagerExternalState::AuthzManagerExternalState() = default;
AuthzManagerExternalState::~AuthzManagerExternalState() = default;
-bool AuthzManagerExternalState::shouldUseRolesFromConnection(OperationContext* opCtx,
- const UserName& userName) {
-#ifdef MONGO_CONFIG_SSL
- if (!opCtx || !opCtx->getClient() || !opCtx->getClient()->session()) {
- return false;
- }
-
- if (!allowRolesFromX509Certificates) {
- return false;
- }
-
- auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
- return sslPeerInfo.subjectName.toString() == userName.getUser() &&
- userName.getDB() == "$external" && !sslPeerInfo.roles.empty();
-#else
- return false;
-#endif
-}
-
-
} // namespace mongo
diff --git a/src/mongo/db/auth/authz_manager_external_state.h b/src/mongo/db/auth/authz_manager_external_state.h
index 3a98677cedd..7dde1480d96 100644
--- a/src/mongo/db/auth/authz_manager_external_state.h
+++ b/src/mongo/db/auth/authz_manager_external_state.h
@@ -83,18 +83,20 @@ public:
virtual Status getStoredAuthorizationVersion(OperationContext* opCtx, int* outVersion) = 0;
/**
- * Writes into "result" a document describing the named user and returns Status::OK(). The
- * description includes the user credentials and customData, if present, the user's role
- * membership and delegation information, a full list of the user's privileges, and a full
- * list of the user's roles, including those roles held implicitly through other roles
- * (indirect roles). In the event that some of this information is inconsistent, the
- * document will contain a "warnings" array, with std::string messages describing
- * inconsistencies.
+ * Writes into "result" a document describing the requested user and returns Status::OK().
+ * The caller is required to provide all information necessary to unique identify the request
+ * for a user, including the user's name and any roles which the user must possess via
+ * out-of-band attestation. The returned description includes the user credentials and
+ * customData, if present, the user's role membership and delegation information, a full
+ * list of the user's privileges, and a full list of the user's roles, including those
+ * roles held implicitly through other roles (indirect roles). In the event that some of this
+ * information is inconsistent, the document will contain a "warnings" array,
+ * with std::string messages describing inconsistencies.
*
* If the user does not exist, returns ErrorCodes::UserNotFound.
*/
virtual Status getUserDescription(OperationContext* opCtx,
- const UserName& userName,
+ const UserRequest& user,
BSONObj* result) = 0;
/**
@@ -171,12 +173,6 @@ public:
protected:
AuthzManagerExternalState(); // This class should never be instantiated directly.
-
- /**
- * Returns true if roles for this user were provided by the client, and can be obtained from
- * the connection.
- */
- bool shouldUseRolesFromConnection(OperationContext* opCtx, const UserName& username);
};
} // namespace mongo
diff --git a/src/mongo/db/auth/authz_manager_external_state_local.cpp b/src/mongo/db/auth/authz_manager_external_state_local.cpp
index 15ec6f917fe..eafeeb5dd9b 100644
--- a/src/mongo/db/auth/authz_manager_external_state_local.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state_local.cpp
@@ -162,19 +162,19 @@ bool AuthzManagerExternalStateLocal::hasAnyPrivilegeDocuments(OperationContext*
}
Status AuthzManagerExternalStateLocal::getUserDescription(OperationContext* opCtx,
- const UserName& userName,
+ const UserRequest& userReq,
BSONObj* result) {
Status status = Status::OK();
+ const UserName& userName = userReq.name;
- if (!shouldUseRolesFromConnection(opCtx, userName)) {
+ if (!userReq.roles) {
status = _getUserDocument(opCtx, userName, result);
if (!status.isOK())
return status;
} else {
// We are able to artifically construct the external user from the request
BSONArrayBuilder userRoles;
- auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
- for (const RoleName& role : sslPeerInfo.roles) {
+ for (const RoleName& role : *(userReq.roles)) {
userRoles << BSON("role" << role.getRole() << "db" << role.getDB());
}
*result = BSON("_id" << userName.getUser() << "user" << userName.getUser() << "db"
diff --git a/src/mongo/db/auth/authz_manager_external_state_local.h b/src/mongo/db/auth/authz_manager_external_state_local.h
index a5965abf3c2..5fcff02bcc6 100644
--- a/src/mongo/db/auth/authz_manager_external_state_local.h
+++ b/src/mongo/db/auth/authz_manager_external_state_local.h
@@ -60,7 +60,7 @@ public:
Status getStoredAuthorizationVersion(OperationContext* opCtx, int* outVersion) override;
Status getUserDescription(OperationContext* opCtx,
- const UserName& userName,
+ const UserRequest& user,
BSONObj* result) override;
Status getRoleDescription(OperationContext* opCtx,
const RoleName& roleName,
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 d8835c5a15a..09c34c86e9a 100644
--- a/src/mongo/db/auth/authz_manager_external_state_s.cpp
+++ b/src/mongo/db/auth/authz_manager_external_state_s.cpp
@@ -115,9 +115,10 @@ Status AuthzManagerExternalStateMongos::getStoredAuthorizationVersion(OperationC
}
Status AuthzManagerExternalStateMongos::getUserDescription(OperationContext* opCtx,
- const UserName& userName,
+ const UserRequest& user,
BSONObj* result) {
- if (!shouldUseRolesFromConnection(opCtx, userName)) {
+ const UserName& userName = user.name;
+ if (!user.roles) {
BSONObj usersInfoCmd =
BSON("usersInfo" << BSON_ARRAY(BSON(AuthorizationManager::USER_NAME_FIELD_NAME
<< userName.getUser()
@@ -151,8 +152,7 @@ Status AuthzManagerExternalStateMongos::getUserDescription(OperationContext* opC
// Obtain privilege information from the config servers for all roles acquired from the X509
// certificate.
BSONArrayBuilder userRolesBuilder;
- auto& sslPeerInfo = SSLPeerInfo::forSession(opCtx->getClient()->session());
- for (const RoleName& role : sslPeerInfo.roles) {
+ for (const RoleName& role : *user.roles) {
userRolesBuilder.append(BSON(
AuthorizationManager::ROLE_NAME_FIELD_NAME
<< role.getRole() << AuthorizationManager::ROLE_DB_FIELD_NAME << role.getDB()));
diff --git a/src/mongo/db/auth/authz_manager_external_state_s.h b/src/mongo/db/auth/authz_manager_external_state_s.h
index 4e20418a446..939a2ca1cb2 100644
--- a/src/mongo/db/auth/authz_manager_external_state_s.h
+++ b/src/mongo/db/auth/authz_manager_external_state_s.h
@@ -56,7 +56,7 @@ public:
AuthorizationManager* authzManager) override;
Status getStoredAuthorizationVersion(OperationContext* opCtx, int* outVersion) override;
Status getUserDescription(OperationContext* opCtx,
- const UserName& userName,
+ const UserRequest& user,
BSONObj* result) override;
Status getRoleDescription(OperationContext* opCtx,
const RoleName& roleName,
diff --git a/src/mongo/db/auth/user.h b/src/mongo/db/auth/user.h
index 97c9994d2e9..59d94699c04 100644
--- a/src/mongo/db/auth/user.h
+++ b/src/mongo/db/auth/user.h
@@ -254,7 +254,37 @@ private:
RestrictionDocuments _restrictions;
};
-using UserCache = ReadThroughCache<UserName, User>;
+/**
+ * Represents the properties required to request a UserHandle.
+ * This type is hashable and may be used as a key describing requests
+ */
+struct UserRequest {
+ UserRequest(const UserName& name, boost::optional<std::set<RoleName>> roles)
+ : name(name), roles(std::move(roles)) {}
+
+
+ template <typename H>
+ friend H AbslHashValue(H h, const UserRequest& key) {
+ auto state = H::combine(std::move(h), key.name);
+ if (key.roles) {
+ for (const auto& role : *key.roles) {
+ state = H::combine(std::move(state), role);
+ }
+ }
+ return state;
+ }
+
+ bool operator==(const UserRequest& key) const {
+ return name == key.name && roles == key.roles;
+ }
+
+ // The name of the requested user
+ UserName name;
+ // Any authorization grants which should override and be used in favor of roles acquisition.
+ boost::optional<std::set<RoleName>> roles;
+};
+
+using UserCache = ReadThroughCache<UserRequest, User>;
using UserHandle = UserCache::ValueHandle;
} // namespace mongo