diff options
author | Mark Benvenuto <mark.benvenuto@mongodb.com> | 2021-03-21 22:46:19 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-03-22 03:34:46 +0000 |
commit | 68dbfa5edbb55fec190e508607cf2276367f4f93 (patch) | |
tree | e1999ab678707adde4dd7f436dd07c8292cc4970 /src/mongo/db/auth | |
parent | 393fe9da6a544af2aaea88eda037cfa3eea0da6d (diff) | |
download | mongo-68dbfa5edbb55fec190e508607cf2276367f4f93.tar.gz |
SERVER-54524 Extend Authorization Session to record all access checks and privilege checks.
Diffstat (limited to 'src/mongo/db/auth')
-rw-r--r-- | src/mongo/db/auth/SConscript | 2 | ||||
-rw-r--r-- | src/mongo/db/auth/access_checks.idl | 25 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_contract.cpp | 45 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_contract.h | 5 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session.h | 16 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_impl.cpp | 105 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_impl.h | 22 | ||||
-rw-r--r-- | src/mongo/db/auth/authorization_session_test.cpp | 31 | ||||
-rw-r--r-- | src/mongo/db/auth/impersonation_session.h | 4 |
9 files changed, 240 insertions, 15 deletions
diff --git a/src/mongo/db/auth/SConscript b/src/mongo/db/auth/SConscript index 4725a1b3362..a068a11714b 100644 --- a/src/mongo/db/auth/SConscript +++ b/src/mongo/db/auth/SConscript @@ -143,7 +143,6 @@ env.Library( env.Library( target='auth_impl_internal', source=[ - 'access_checks.idl', 'authorization_contract.idl', 'authorization_manager_impl.cpp', 'authorization_session_impl.cpp', @@ -211,6 +210,7 @@ env.Library( env.Library( target='authprivilege', source=[ + 'access_checks.idl', 'action_set.cpp', 'action_type.cpp', 'action_type.idl', diff --git a/src/mongo/db/auth/access_checks.idl b/src/mongo/db/auth/access_checks.idl index cc5b6955cc2..99ded87fd03 100644 --- a/src/mongo/db/auth/access_checks.idl +++ b/src/mongo/db/auth/access_checks.idl @@ -33,6 +33,25 @@ enums: description: "List of supported access checks in AuthorizationSession" type: string values: - kIsAuthenticated : "is_authenticated" - kIsCoAuthorized : "is_coauthorized" - kIsAuthorizedToParseNamespaceElement : "is_authorized_to_parse_namespace_element" + kCheckAuthorizedToListCollections : "check_authorized_to_list_collections" + kCheckCursorSessionPrivilege : "check_cursor_session_privilege" + kClearImpersonatedUserData : "clear_impersonated_user_data" + kGetAuthenticatedRoleNames : "get_authenticated_role_names" + kGetAuthenticatedUserNames : "get_authenticated_user_names" + kGetImpersonatedRoleNames : "get_impersonated_role_names" + kGetImpersonatedUserNames : "get_impersonated_user_names" + kGetSingleUser : "get_single_user" + kIsAuthenticated : "is_authenticated" + kIsAuthenticatedAsUserWithRole : "is_authenticated_as_user_with_role" + kIsAuthorizedForAnyActionOnAnyResourceInDB : "is_authorized_for_any_action_on_any_resource_in_db" + kIsAuthorizedForAnyActionOnResource : "is_authorized_for_any_action_on_resource" + kIsAuthorizedToChangeAsUser : "is_authorized_to_change_as_user" + kIsAuthorizedToCreateRole : "is_authorized_to_create_role" + kIsAuthorizedToParseNamespaceElement : "is_authorized_to_parse_namespace_element" + kIsCoAuthorized : "is_coauthorized" + kIsCoauthorizedWith : "is_coauthorized_with" + kIsCoauthorizedWithClient : "is_coauthorized_with_client" + kIsImpersonating : "is_impersonating" + kLookupUser : "lookup_user" + kShouldIgnoreAuthChecks : "should_ignore_auth_checks" + kIsUsingLocalhostBypass : "is_using_localhost_bypass" # Called in common code in commands.cpp dispatch
\ No newline at end of file diff --git a/src/mongo/db/auth/authorization_contract.cpp b/src/mongo/db/auth/authorization_contract.cpp index 7a0d3985f4a..6f78c07f150 100644 --- a/src/mongo/db/auth/authorization_contract.cpp +++ b/src/mongo/db/auth/authorization_contract.cpp @@ -27,11 +27,26 @@ * it in the license file. */ +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kAccessControl + #include "mongo/db/auth/authorization_contract.h" + +#include "mongo/bson/bsontypes.h" +#include "mongo/db/auth/access_checks_gen.h" +#include "mongo/db/auth/action_type_gen.h" #include "mongo/db/auth/privilege.h" +#include "mongo/logv2/log.h" +#include "mongo/util/debug_util.h" namespace mongo { +void AuthorizationContract::clear() { + _checks.reset(); + for (size_t i = 0; i < _privilegeChecks.size(); ++i) { + _privilegeChecks[i].removeAllActions(); + } +} + void AuthorizationContract::addAccessCheck(AccessCheckEnum check) { _checks.set(static_cast<size_t>(check), true); } @@ -55,11 +70,41 @@ bool AuthorizationContract::hasPrivileges(const Privilege& p) const { bool AuthorizationContract::contains(const AuthorizationContract& other) const { if ((_checks | other._checks) != _checks) { + if (kDebugBuild) { + auto missingChecks = (_checks ^ other._checks) & other._checks; + + BSONArrayBuilder builder; + for (size_t i = 0; i < missingChecks.size(); i++) { + if (missingChecks.test(i)) { + builder.append(AccessCheck_serializer(static_cast<AccessCheckEnum>(i))); + } + } + + LOGV2(5452402, "Missing Auth Checks", "checks"_attr = builder.arr()); + } + return false; } for (size_t i = 0; i < _privilegeChecks.size(); ++i) { if (!_privilegeChecks[i].contains(other._privilegeChecks[i])) { + if (kDebugBuild) { + BSONArrayBuilder builder; + + for (size_t k = 0; k < kNumActionTypes; k++) { + auto at = static_cast<ActionTypeEnum>(k); + if (other._privilegeChecks[i].contains(at) && + !_privilegeChecks[i].contains(at)) { + builder.append(ActionType_serializer(at)); + } + } + + LOGV2(5452403, + "Missing Action Types for resource", + "resource"_attr = MatchType_serializer(static_cast<MatchTypeEnum>(i)), + "actions"_attr = builder.arr()); + } + return false; } } diff --git a/src/mongo/db/auth/authorization_contract.h b/src/mongo/db/auth/authorization_contract.h index b5aba515573..2541481d72e 100644 --- a/src/mongo/db/auth/authorization_contract.h +++ b/src/mongo/db/auth/authorization_contract.h @@ -68,6 +68,11 @@ public: } /** + * Clear the authorization contract + */ + void clear(); + + /** * Add a access check to the contract. */ void addAccessCheck(AccessCheckEnum check); diff --git a/src/mongo/db/auth/authorization_session.h b/src/mongo/db/auth/authorization_session.h index 08892418c59..8ccb589f344 100644 --- a/src/mongo/db/auth/authorization_session.h +++ b/src/mongo/db/auth/authorization_session.h @@ -47,6 +47,7 @@ namespace mongo { class Client; +class AuthorizationContract; /** * Contains all the authorization logic for a single client connection. It contains a set of @@ -136,6 +137,11 @@ public: virtual void startRequest(OperationContext* opCtx) = 0; /** + * Start tracking permissions and privileges in the authorization contract. + */ + virtual void startContractTracking() = 0; + + /** * Adds the User identified by "UserName" to the authorization session, acquiring privileges * for it in the process. */ @@ -178,13 +184,6 @@ public: virtual void grantInternalAuthorization(Client* client) = 0; virtual void grantInternalAuthorization(OperationContext* opCtx) = 0; - // Generates a vector of default privileges that are granted to any user, - // regardless of which roles that user does or does not possess. - // If localhost exception is active, the permissions include the ability to create - // the first user and the ability to run the commands needed to bootstrap the system - // into a state where the first user can be created. - virtual PrivilegeVector getDefaultPrivileges() = 0; - // Checks if the current session is authorized to list the collections in the given // database. If it is, return a privilegeVector containing the privileges used to authorize // this command. @@ -290,6 +289,9 @@ public: virtual Status checkCursorSessionPrivilege( OperationContext* const opCtx, boost::optional<LogicalSessionId> cursorSessionId) = 0; + // Verify the authorization contract. If contract == nullptr, no check is performed. + virtual void verifyContract(const AuthorizationContract* contract) const = 0; + protected: virtual std::tuple<std::vector<UserName>*, std::vector<RoleName>*> _getImpersonations() = 0; }; diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index a6f09b04ff7..0686c974bcb 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -42,6 +42,7 @@ #include "mongo/db/audit.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" +#include "mongo/db/auth/authorization_contract_gen.h" #include "mongo/db/auth/authz_session_external_state.h" #include "mongo/db/auth/privilege.h" #include "mongo/db/bson/dotted_path_support.h" @@ -51,6 +52,7 @@ #include "mongo/logv2/log.h" #include "mongo/util/assert_util.h" #include "mongo/util/str.h" +#include "mongo/util/testing_proctor.h" namespace mongo { @@ -70,6 +72,24 @@ auto authorizationSessionCreateRegistration = constexpr StringData ADMIN_DBNAME = "admin"_sd; +bool checkContracts() { + + // Only check contracts if the feature is enabled. + // TODO SERVER-52364 - Remove feature flag check + if (!serverGlobalParams.featureCompatibility.isVersionInitialized() || + !feature_flags::gFeatureFlagAuthorizationContract.isEnabled( + serverGlobalParams.featureCompatibility)) { + return false; + } + + // Only check contracts in testing modes, invalid contracts should not break customers. + if (!TestingProctor::instance().isEnabled()) { + return false; + } + + return true; +} + } // namespace AuthorizationSessionImpl::AuthorizationSessionImpl( @@ -90,6 +110,14 @@ void AuthorizationSessionImpl::startRequest(OperationContext* opCtx) { _refreshUserInfoAsNeeded(opCtx); } +void AuthorizationSessionImpl::startContractTracking() { + if (!checkContracts()) { + return; + } + + _contract.clear(); +} + Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, const UserName& userName) { AuthorizationManager* authzManager = AuthorizationManager::get(opCtx->getServiceContext()); @@ -120,6 +148,8 @@ Status AuthorizationSessionImpl::addAndAuthorizeUser(OperationContext* opCtx, } User* AuthorizationSessionImpl::lookupUser(const UserName& name) { + _contract.addAccessCheck(AccessCheckEnum::kLookupUser); + auto user = _authenticatedUsers.lookup(name); return user ? user.get() : nullptr; } @@ -127,6 +157,8 @@ User* AuthorizationSessionImpl::lookupUser(const UserName& name) { User* AuthorizationSessionImpl::getSingleUser() { UserName userName; + _contract.addAccessCheck(AccessCheckEnum::kGetSingleUser); + auto userNameItr = getAuthenticatedUserNames(); if (userNameItr.more()) { userName = userNameItr.next(); @@ -176,10 +208,14 @@ void AuthorizationSessionImpl::logoutDatabase(Client* client, } UserNameIterator AuthorizationSessionImpl::getAuthenticatedUserNames() { + _contract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedUserNames); + return _authenticatedUsers.getNames(); } RoleNameIterator AuthorizationSessionImpl::getAuthenticatedRoleNames() { + _contract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedRoleNames); + return makeRoleNameIterator(_authenticatedRoleNames.begin(), _authenticatedRoleNames.end()); } @@ -197,7 +233,7 @@ void AuthorizationSessionImpl::grantInternalAuthorization(OperationContext* opCt grantInternalAuthorization(opCtx->getClient()); } -PrivilegeVector AuthorizationSessionImpl::getDefaultPrivileges() { +PrivilegeVector AuthorizationSessionImpl::_getDefaultPrivileges() { PrivilegeVector defaultPrivileges; // If localhost exception is active (and no users exist), @@ -242,6 +278,7 @@ PrivilegeVector AuthorizationSessionImpl::getDefaultPrivileges() { bool AuthorizationSessionImpl::isAuthorizedToParseNamespaceElement(const BSONElement& element) { const bool isUUID = element.type() == BinData && element.binDataType() == BinDataType::newUUID; + _contract.addAccessCheck(AccessCheckEnum::kIsAuthorizedToParseNamespaceElement); uassert(ErrorCodes::InvalidNamespace, "Failed to parse namespace element", @@ -257,6 +294,8 @@ bool AuthorizationSessionImpl::isAuthorizedToParseNamespaceElement(const BSONEle bool AuthorizationSessionImpl::isAuthorizedToParseNamespaceElement( const NamespaceStringOrUUID& nss) { + _contract.addAccessCheck(AccessCheckEnum::kIsAuthorizedToParseNamespaceElement); + if (nss.uuid()) { return isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), ActionType::useUUID); @@ -265,6 +304,8 @@ bool AuthorizationSessionImpl::isAuthorizedToParseNamespaceElement( } bool AuthorizationSessionImpl::isAuthorizedToCreateRole(const RoleName& roleName) { + _contract.addAccessCheck(AccessCheckEnum::kIsAuthorizedToCreateRole); + // A user is allowed to create a role under either of two conditions. // The user may create a role if the authorization system says they are allowed to. @@ -397,6 +438,8 @@ static int buildResourceSearchList(const ResourcePattern& target, bool AuthorizationSessionImpl::isAuthorizedToChangeAsUser(const UserName& userName, ActionType actionType) { + _contract.addAccessCheck(AccessCheckEnum::kIsAuthorizedToChangeAsUser); + User* user = lookupUser(userName); if (!user) { return false; @@ -414,6 +457,8 @@ bool AuthorizationSessionImpl::isAuthorizedToChangeAsUser(const UserName& userNa StatusWith<PrivilegeVector> AuthorizationSessionImpl::checkAuthorizedToListCollections( StringData dbname, const BSONObj& cmdObj) { + _contract.addAccessCheck(AccessCheckEnum::kCheckAuthorizedToListCollections); + if (cmdObj["authorizedCollections"].trueValue() && cmdObj["nameOnly"].trueValue() && AuthorizationSessionImpl::isAuthorizedForAnyActionOnAnyResourceInDB(dbname)) { return PrivilegeVector(); @@ -431,6 +476,8 @@ StatusWith<PrivilegeVector> AuthorizationSessionImpl::checkAuthorizedToListColle } bool AuthorizationSessionImpl::isAuthenticatedAsUserWithRole(const RoleName& roleName) { + _contract.addAccessCheck(AccessCheckEnum::kIsAuthenticatedAsUserWithRole); + for (UserSet::iterator it = _authenticatedUsers.begin(); it != _authenticatedUsers.end(); ++it) { if ((*it)->hasRole(roleName)) { @@ -441,10 +488,14 @@ bool AuthorizationSessionImpl::isAuthenticatedAsUserWithRole(const RoleName& rol } bool AuthorizationSessionImpl::shouldIgnoreAuthChecks() { + _contract.addAccessCheck(AccessCheckEnum::kShouldIgnoreAuthChecks); + return _externalState->shouldIgnoreAuthChecks(); } bool AuthorizationSessionImpl::isAuthenticated() { + _contract.addAccessCheck(AccessCheckEnum::kIsAuthenticated); + return _authenticatedUsers.begin() != _authenticatedUsers.end(); } @@ -550,6 +601,8 @@ void AuthorizationSessionImpl::_buildAuthenticatedRolesVector() { } bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnAnyResourceInDB(StringData db) { + _contract.addAccessCheck(AccessCheckEnum::kIsAuthorizedForAnyActionOnAnyResourceInDB); + if (_externalState->shouldIgnoreAuthChecks()) { return true; } @@ -595,6 +648,8 @@ bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnAnyResourceInDB(StringD } bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnResource(const ResourcePattern& resource) { + _contract.addAccessCheck(AccessCheckEnum::kIsAuthorizedForAnyActionOnResource); + if (_externalState->shouldIgnoreAuthChecks()) { return true; } @@ -616,6 +671,8 @@ bool AuthorizationSessionImpl::isAuthorizedForAnyActionOnResource(const Resource bool AuthorizationSessionImpl::_isAuthorizedForPrivilege(const Privilege& privilege) { + _contract.addPrivilege(privilege); + const ResourcePattern& target(privilege.getResourcePattern()); ResourcePattern resourceSearchList[resourceSearchListCapacity]; @@ -623,7 +680,7 @@ bool AuthorizationSessionImpl::_isAuthorizedForPrivilege(const Privilege& privil ActionSet unmetRequirements = privilege.getActions(); - PrivilegeVector defaultPrivileges = getDefaultPrivileges(); + PrivilegeVector defaultPrivileges = _getDefaultPrivileges(); for (PrivilegeVector::iterator it = defaultPrivileges.begin(); it != defaultPrivileges.end(); ++it) { for (int i = 0; i < resourceSearchListLength; ++i) { @@ -660,6 +717,7 @@ void AuthorizationSessionImpl::setImpersonatedUserData(const std::vector<UserNam } bool AuthorizationSessionImpl::isCoauthorizedWithClient(Client* opClient, WithLock opClientLock) { + _contract.addAccessCheck(AccessCheckEnum::kIsCoauthorizedWithClient); auto getUserNames = [](AuthorizationSession* authSession) { if (authSession->isImpersonating()) { return authSession->getImpersonatedUserNames(); @@ -684,6 +742,7 @@ bool AuthorizationSessionImpl::isCoauthorizedWithClient(Client* opClient, WithLo } bool AuthorizationSessionImpl::isCoauthorizedWith(UserNameIterator userNameIter) { + _contract.addAccessCheck(AccessCheckEnum::kIsCoauthorizedWith); if (!getAuthorizationManager().isAuthEnabled()) { return true; } @@ -706,14 +765,20 @@ bool AuthorizationSessionImpl::isCoauthorizedWith(UserNameIterator userNameIter) } UserNameIterator AuthorizationSessionImpl::getImpersonatedUserNames() { + _contract.addAccessCheck(AccessCheckEnum::kGetImpersonatedUserNames); + return makeUserNameIterator(_impersonatedUserNames.begin(), _impersonatedUserNames.end()); } RoleNameIterator AuthorizationSessionImpl::getImpersonatedRoleNames() { + _contract.addAccessCheck(AccessCheckEnum::kGetImpersonatedRoleNames); + return makeRoleNameIterator(_impersonatedRoleNames.begin(), _impersonatedRoleNames.end()); } bool AuthorizationSessionImpl::isUsingLocalhostBypass() { + _contract.addAccessCheck(AccessCheckEnum::kIsUsingLocalhostBypass); + return getAuthorizationManager().isAuthEnabled() && _externalState->shouldAllowLocalhost(); } @@ -732,6 +797,8 @@ bool AuthorizationSessionImpl::isImpersonating() const { auto AuthorizationSessionImpl::checkCursorSessionPrivilege( OperationContext* const opCtx, const boost::optional<LogicalSessionId> cursorSessionId) -> Status { + _contract.addAccessCheck(AccessCheckEnum::kCheckCursorSessionPrivilege); + auto nobodyIsLoggedIn = [authSession = this] { return !authSession->isAuthenticated(); }; auto authHasImpersonatePrivilege = [authSession = this] { @@ -777,4 +844,38 @@ auto AuthorizationSessionImpl::checkCursorSessionPrivilege( return Status::OK(); } +void AuthorizationSessionImpl::verifyContract(const AuthorizationContract* contract) const { + if (contract == nullptr) { + return; + } + + if (!checkContracts()) { + return; + } + + // Make a mutable copy so that the common auth checks can be added. + auto tempContract = *contract; + + // Certain access checks are done by code common to all commands. + // + // The first two checks are done by initializeOperationSessionInfo + tempContract.addAccessCheck(AccessCheckEnum::kIsUsingLocalhostBypass); + tempContract.addAccessCheck(AccessCheckEnum::kIsAuthenticated); + + // These checks are done by auditing + tempContract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedRoleNames); + tempContract.addAccessCheck(AccessCheckEnum::kGetAuthenticatedUserNames); + tempContract.addAccessCheck(AccessCheckEnum::kGetImpersonatedUserNames); + tempContract.addAccessCheck(AccessCheckEnum::kGetImpersonatedRoleNames); + + // "internal" comes from readRequestMetadata and sharded clusters + // "advanceClusterTime" is an implicit check in clusters in metadata handling + tempContract.addPrivilege(Privilege(ResourcePattern::forClusterResource(), + {ActionType::advanceClusterTime, ActionType::internal})); + + uassert(5452401, + "Authorization Session contains more authorization checks then permitted by contract.", + tempContract.contains(_contract)); +} + } // namespace mongo diff --git a/src/mongo/db/auth/authorization_session_impl.h b/src/mongo/db/auth/authorization_session_impl.h index fa1848680e9..8887a06fc4b 100644 --- a/src/mongo/db/auth/authorization_session_impl.h +++ b/src/mongo/db/auth/authorization_session_impl.h @@ -35,6 +35,7 @@ #include "mongo/base/status.h" #include "mongo/db/auth/action_set.h" #include "mongo/db/auth/action_type.h" +#include "mongo/db/auth/authorization_contract.h" #include "mongo/db/auth/authorization_session.h" #include "mongo/db/auth/authz_session_external_state.h" #include "mongo/db/auth/privilege.h" @@ -74,6 +75,8 @@ public: void startRequest(OperationContext* opCtx) override; + void startContractTracking() override; + Status addAndAuthorizeUser(OperationContext* opCtx, const UserName& userName) override; User* lookupUser(const UserName& name) override; @@ -95,8 +98,6 @@ public: void grantInternalAuthorization(OperationContext* opCtx) override; - PrivilegeVector getDefaultPrivileges() override; - StatusWith<PrivilegeVector> checkAuthorizedToListCollections(StringData dbname, const BSONObj& cmdObj) override; @@ -149,6 +150,8 @@ public: Status checkCursorSessionPrivilege(OperationContext* const opCtx, boost::optional<LogicalSessionId> cursorSessionId) override; + void verifyContract(const AuthorizationContract* contract) const override; + protected: // Builds a vector of all roles held by users who are authenticated on this connection. The // vector is stored in _authenticatedRoleNames. This function is called when users are @@ -177,6 +180,15 @@ private: return std::make_tuple(&_impersonatedUserNames, &_impersonatedRoleNames); } + + // Generates a vector of default privileges that are granted to any user, + // regardless of which roles that user does or does not possess. + // If localhost exception is active, the permissions include the ability to create + // the first user and the ability to run the commands needed to bootstrap the system + // into a state where the first user can be created. + PrivilegeVector _getDefaultPrivileges(); + +private: std::unique_ptr<AuthzSessionExternalState> _externalState; // A vector of impersonated UserNames and a vector of those users' RoleNames. @@ -184,5 +196,11 @@ private: std::vector<UserName> _impersonatedUserNames; std::vector<RoleName> _impersonatedRoleNames; bool _impersonationFlag; + + // A record of privilege checks and other authorization like function calls made on + // AuthorizationSession. IDL Typed Commands can optionally define a contract declaring the set + // of authorization checks they perform. After a command completes running, MongoDB verifies the + // set of checks performed is a subset of the checks declared in the contract. + AuthorizationContract _contract; }; } // namespace mongo diff --git a/src/mongo/db/auth/authorization_session_test.cpp b/src/mongo/db/auth/authorization_session_test.cpp index 50f348d991f..689f3642e9e 100644 --- a/src/mongo/db/auth/authorization_session_test.cpp +++ b/src/mongo/db/auth/authorization_session_test.cpp @@ -50,6 +50,7 @@ #include "mongo/db/operation_context.h" #include "mongo/db/pipeline/aggregation_request_helper.h" #include "mongo/db/service_context_test_fixture.h" +#include "mongo/idl/server_parameter_test_util.h" #include "mongo/transport/session.h" #include "mongo/transport/transport_layer_mock.h" #include "mongo/unittest/unittest.h" @@ -125,6 +126,7 @@ protected: AuthorizationManager* authzManager; std::unique_ptr<AuthorizationSessionForTest> authzSession; BSONObj credentials; + RAIIServerParameterControllerForTest controller{"featureFlagAuthorizationContract", 1}; }; const NamespaceString testFooNss("test.foo"); @@ -238,6 +240,26 @@ TEST_F(AuthorizationSessionTest, AddUserAndCheckAuthorization) { authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::insert)); ASSERT_FALSE( authzSession->isAuthorizedForActionsOnResource(testFooCollResource, ActionType::collMod)); + + // Verify we recorded the all the auth checks correctly + AuthorizationContract ac( + std::initializer_list<AccessCheckEnum>{}, + std::initializer_list<Privilege>{ + Privilege(ResourcePattern::forDatabaseName("ignored"), + {ActionType::insert, ActionType::dbStats}), + Privilege(ResourcePattern::forExactNamespace(NamespaceString("ignored.ignored")), + {ActionType::insert, ActionType::collMod}), + }); + + authzSession->verifyContract(&ac); + + // Verify against a smaller contract that verifyContract fails + AuthorizationContract acMissing(std::initializer_list<AccessCheckEnum>{}, + std::initializer_list<Privilege>{ + Privilege(ResourcePattern::forDatabaseName("ignored"), + {ActionType::insert, ActionType::dbStats}), + }); + ASSERT_THROWS_CODE(authzSession->verifyContract(&acMissing), AssertionException, 5452401); } TEST_F(AuthorizationSessionTest, DuplicateRolesOK) { @@ -1343,6 +1365,15 @@ TEST_F(AuthorizationSessionTest, CanUseUUIDNamespacesWithPrivilege) { ASSERT_THROWS_CODE(authzSession->isAuthorizedToParseNamespaceElement(invalidObj.firstElement()), AssertionException, ErrorCodes::InvalidNamespace); + + // Verify we recorded the all the auth checks correctly + AuthorizationContract ac( + std::initializer_list<AccessCheckEnum>{ + AccessCheckEnum::kIsAuthorizedToParseNamespaceElement}, + std::initializer_list<Privilege>{ + Privilege(ResourcePattern::forClusterResource(), ActionType::useUUID)}); + + authzSession->verifyContract(&ac); } diff --git a/src/mongo/db/auth/impersonation_session.h b/src/mongo/db/auth/impersonation_session.h index bab75018f8b..9c15797b364 100644 --- a/src/mongo/db/auth/impersonation_session.h +++ b/src/mongo/db/auth/impersonation_session.h @@ -44,6 +44,10 @@ public: ImpersonationSessionGuard(OperationContext* opCtx); ~ImpersonationSessionGuard(); + bool isActive() const { + return _active; + } + private: rpc::MaybeImpersonatedUserMetadata _oldImpersonationData; OperationContext* _opCtx; |