diff options
author | Spencer Jackson <spencer.jackson@mongodb.com> | 2020-02-28 19:45:13 -0500 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-03-03 00:32:53 +0000 |
commit | a1f6d2eb74268b140929c3f280c37299617d596a (patch) | |
tree | 55df6bd729ca2c663c7c75cd878bfb05d5fd732e /src | |
parent | b7dfdde9d038433cf34e77618d4b1b114d3f4d91 (diff) | |
download | mongo-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.cpp | 62 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_impl.h | 2 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_manager_test.cpp | 4 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.cpp | 21 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state.h | 24 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_local.h | 2 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.cpp | 8 | ||||
-rw-r--r-- | src/mongo/db/auth/authz_manager_external_state_s.h | 2 | ||||
-rw-r--r-- | src/mongo/db/auth/user.h | 32 |
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 |