diff options
Diffstat (limited to 'src/mongo/db/auth/authorization_session_impl.cpp')
-rw-r--r-- | src/mongo/db/auth/authorization_session_impl.cpp | 329 |
1 files changed, 5 insertions, 324 deletions
diff --git a/src/mongo/db/auth/authorization_session_impl.cpp b/src/mongo/db/auth/authorization_session_impl.cpp index 0f131648506..20ed5d956a6 100644 --- a/src/mongo/db/auth/authorization_session_impl.cpp +++ b/src/mongo/db/auth/authorization_session_impl.cpp @@ -33,6 +33,7 @@ #include "mongo/db/auth/authorization_session_impl.h" +#include <array> #include <string> #include <vector> @@ -43,17 +44,10 @@ #include "mongo/db/auth/action_type.h" #include "mongo/db/auth/authz_session_external_state.h" #include "mongo/db/auth/privilege.h" -#include "mongo/db/auth/restriction_environment.h" -#include "mongo/db/auth/security_key.h" -#include "mongo/db/auth/user_management_commands_parser.h" #include "mongo/db/bson/dotted_path_support.h" -#include "mongo/db/catalog/document_validation.h" #include "mongo/db/client.h" -#include "mongo/db/jsobj.h" #include "mongo/db/namespace_string.h" #include "mongo/db/operation_context.h" -#include "mongo/db/pipeline/aggregation_request_helper.h" -#include "mongo/db/pipeline/lite_parsed_pipeline.h" #include "mongo/logv2/log.h" #include "mongo/util/assert_util.h" #include "mongo/util/str.h" @@ -62,7 +56,6 @@ namespace mongo { namespace dps = ::mongo::dotted_path_support; using std::vector; - namespace { std::unique_ptr<AuthorizationSession> authorizationSessionCreateImpl( @@ -77,34 +70,6 @@ auto authorizationSessionCreateRegistration = constexpr StringData ADMIN_DBNAME = "admin"_sd; -// Checks if this connection has the privileges necessary to create or modify the view 'viewNs' -// to be a view on 'viewOnNs' with pipeline 'viewPipeline'. Call this function after verifying -// that the user has the 'createCollection' or 'collMod' action, respectively. -Status checkAuthForCreateOrModifyView(AuthorizationSession* authzSession, - const NamespaceString& viewNs, - const NamespaceString& viewOnNs, - const BSONArray& viewPipeline, - bool isMongos) { - // It's safe to allow a user to create or modify a view if they can't read it anyway. - if (!authzSession->isAuthorizedForActionsOnNamespace(viewNs, ActionType::find)) { - return Status::OK(); - } - - auto request = aggregation_request_helper::parseFromBSON( - viewNs, - BSON("aggregate" << viewOnNs.coll() << "pipeline" << viewPipeline << "cursor" << BSONObj() - << "$db" << viewOnNs.db()), - boost::none, - false); - - auto statusWithPrivs = authzSession->getPrivilegesForAggregate(viewOnNs, request, isMongos); - PrivilegeVector privileges = uassertStatusOK(statusWithPrivs); - if (!authzSession->isAuthorizedForPrivileges(privileges)) { - return Status(ErrorCodes::Unauthorized, "unauthorized"); - } - return Status::OK(); -} - } // namespace AuthorizationSessionImpl::AuthorizationSessionImpl( @@ -267,274 +232,6 @@ PrivilegeVector AuthorizationSessionImpl::getDefaultPrivileges() { return defaultPrivileges; } -StatusWith<PrivilegeVector> AuthorizationSessionImpl::getPrivilegesForAggregate( - const NamespaceString& nss, const AggregateCommand& request, bool isMongos) { - if (!nss.isValid()) { - return Status(ErrorCodes::InvalidNamespace, - str::stream() << "Invalid input namespace, " << nss.ns()); - } - - PrivilegeVector privileges; - - // If this connection does not need to be authenticated (for instance, if auth is disabled), - // returns an empty requirements set. - if (_externalState->shouldIgnoreAuthChecks()) { - return privileges; - } - - const auto& pipeline = request.getPipeline(); - - // If the aggregation pipeline is empty, confirm the user is authorized for find on 'nss'. - if (pipeline.empty()) { - Privilege currentPriv = - Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find); - Privilege::addPrivilegeToPrivilegeVector(&privileges, currentPriv); - return privileges; - } - - // If the first stage of the pipeline is not an initial source, the pipeline is implicitly - // reading documents from the underlying collection. The client must be authorized to do so. - auto liteParsedDocSource = LiteParsedDocumentSource::parse(nss, pipeline[0]); - if (!liteParsedDocSource->isInitialSource()) { - Privilege currentPriv = - Privilege(ResourcePattern::forExactNamespace(nss), ActionType::find); - Privilege::addPrivilegeToPrivilegeVector(&privileges, currentPriv); - } - - // Confirm privileges for the pipeline. - for (auto&& pipelineStage : pipeline) { - liteParsedDocSource = LiteParsedDocumentSource::parse(nss, pipelineStage); - PrivilegeVector currentPrivs = liteParsedDocSource->requiredPrivileges( - isMongos, request.getBypassDocumentValidation().value_or(false)); - Privilege::addPrivilegesToPrivilegeVector(&privileges, currentPrivs); - } - return privileges; -} - -Status AuthorizationSessionImpl::checkAuthForFind(const NamespaceString& ns, bool hasTerm) { - if (MONGO_unlikely(ns.isCommand())) { - return Status(ErrorCodes::InternalError, - str::stream() << "Checking query auth on command namespace " << ns.ns()); - } - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::find)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for query on " << ns.ns()); - } - - // Only internal clients (such as other nodes in a replica set) are allowed to use - // the 'term' field in a find operation. Use of this field could trigger changes - // in the receiving server's replication state and should be protected. - if (hasTerm && - !isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), - ActionType::internal)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for query with term on " << ns.ns()); - } - - return Status::OK(); -} - -Status AuthorizationSessionImpl::checkAuthForGetMore(const NamespaceString& ns, - long long cursorID, - bool hasTerm) { - // Since users can only getMore their own cursors, we verify that a user either is authenticated - // or does not need to be. - if (!_externalState->shouldIgnoreAuthChecks() && !isAuthenticated()) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for getMore on " << ns.db()); - } - - // Only internal clients (such as other nodes in a replica set) are allowed to use - // the 'term' field in a getMore operation. Use of this field could trigger changes - // in the receiving server's replication state and should be protected. - if (hasTerm && - !isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), - ActionType::internal)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for getMore with term on " << ns.ns()); - } - - return Status::OK(); -} - -Status AuthorizationSessionImpl::checkAuthForInsert(OperationContext* opCtx, - const NamespaceString& ns) { - ActionSet required{ActionType::insert}; - if (DocumentValidationSettings::get(opCtx).isSchemaValidationDisabled()) { - required.addAction(ActionType::bypassDocumentValidation); - } - if (!isAuthorizedForActionsOnNamespace(ns, required)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for insert on " << ns.ns()); - } - - return Status::OK(); -} - -Status AuthorizationSessionImpl::checkAuthForUpdate(OperationContext* opCtx, - const NamespaceString& ns, - const BSONObj& query, - const write_ops::UpdateModification& update, - bool upsert) { - ActionSet required{ActionType::update}; - StringData operationType = "update"_sd; - - if (upsert) { - required.addAction(ActionType::insert); - operationType = "upsert"_sd; - } - - if (DocumentValidationSettings::get(opCtx).isSchemaValidationDisabled()) { - required.addAction(ActionType::bypassDocumentValidation); - } - - if (!isAuthorizedForActionsOnNamespace(ns, required)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized for " << operationType << " on " << ns.ns()); - } - - return Status::OK(); -} - -Status AuthorizationSessionImpl::checkAuthForDelete(OperationContext* opCtx, - const NamespaceString& ns, - const BSONObj& query) { - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::remove)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized to remove from " << ns.ns()); - } - return Status::OK(); -} - -Status AuthorizationSessionImpl::checkAuthForKillCursors(const NamespaceString& ns, - UserNameIterator cursorOwner) { - if (isAuthorizedForActionsOnResource(ResourcePattern::forClusterResource(), - ActionType::killAnyCursor)) { - return Status::OK(); - } - - if (isCoauthorizedWith(cursorOwner)) { - return Status::OK(); - } - - ResourcePattern target; - if (ns.isListCollectionsCursorNS()) { - target = ResourcePattern::forDatabaseName(ns.db()); - } else { - target = ResourcePattern::forExactNamespace(ns); - } - - if (isAuthorizedForActionsOnResource(target, ActionType::killAnyCursor)) { - return Status::OK(); - } - - return Status(ErrorCodes::Unauthorized, - str::stream() << "not authorized to kill cursor on " << ns.ns()); -} - -Status AuthorizationSessionImpl::checkAuthForCreate(const CreateCommand& cmd, bool isMongos) { - auto ns = cmd.getNamespace(); - if (cmd.getCapped() && !isAuthorizedForActionsOnNamespace(ns, ActionType::convertToCapped)) { - return {ErrorCodes::Unauthorized, "unauthorized"}; - } - - const bool hasCreateCollectionAction = - isAuthorizedForActionsOnNamespace(ns, ActionType::createCollection); - - // If attempting to create a view, check for additional required privileges. - if (auto optViewOn = cmd.getViewOn()) { - // You need the createCollection action on this namespace; the insert action is not - // sufficient. - if (!hasCreateCollectionAction) { - return {ErrorCodes::Unauthorized, "unauthorized"}; - } - - // Parse the viewOn namespace and the pipeline. If no pipeline was specified, use the empty - // pipeline. - NamespaceString viewOnNs(ns.db(), optViewOn.get()); - auto pipeline = cmd.getPipeline().get_value_or(std::vector<BSONObj>()); - BSONArrayBuilder pipelineArray; - for (const auto& stage : pipeline) { - pipelineArray.append(stage); - } - return checkAuthForCreateOrModifyView(this, ns, viewOnNs, pipelineArray.arr(), isMongos); - } - - // To create a regular collection, ActionType::createCollection or ActionType::insert are - // both acceptable. - if (hasCreateCollectionAction || isAuthorizedForActionsOnNamespace(ns, ActionType::insert)) { - return Status::OK(); - } - - return {ErrorCodes::Unauthorized, "unauthorized"}; -} - -Status AuthorizationSessionImpl::checkAuthForCollMod(const NamespaceString& ns, - const BSONObj& cmdObj, - bool isMongos) { - if (!isAuthorizedForActionsOnNamespace(ns, ActionType::collMod)) { - return Status(ErrorCodes::Unauthorized, "unauthorized"); - } - - // Check for additional required privileges if attempting to modify a view. When auth is - // enabled, users must specify both "viewOn" and "pipeline" together. This prevents a user from - // exposing more information in the original underlying namespace by only changing "pipeline", - // or looking up more information via the original pipeline by only changing "viewOn". - const bool hasViewOn = cmdObj.hasField("viewOn"); - const bool hasPipeline = cmdObj.hasField("pipeline"); - if (hasViewOn != hasPipeline) { - return Status( - ErrorCodes::InvalidOptions, - "Must specify both 'viewOn' and 'pipeline' when modifying a view and auth is enabled"); - } - if (hasViewOn) { - NamespaceString viewOnNs(ns.db(), cmdObj["viewOn"].checkAndGetStringData()); - auto viewPipeline = BSONArray(cmdObj["pipeline"].Obj()); - return checkAuthForCreateOrModifyView(this, ns, viewOnNs, viewPipeline, isMongos); - } - - return Status::OK(); -} - -Status AuthorizationSessionImpl::checkAuthorizedToGrantPrivilege(const Privilege& privilege) { - const ResourcePattern& resource = privilege.getResourcePattern(); - if (resource.isDatabasePattern() || resource.isExactNamespacePattern()) { - if (!isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(resource.databaseToMatch()), - ActionType::grantRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to grant privileges on the " - << resource.databaseToMatch() << "database"); - } - } else if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName("admin"), - ActionType::grantRole)) { - return Status(ErrorCodes::Unauthorized, - "To grant privileges affecting multiple databases or the cluster," - " must be authorized to grant roles from the admin database"); - } - return Status::OK(); -} - - -Status AuthorizationSessionImpl::checkAuthorizedToRevokePrivilege(const Privilege& privilege) { - const ResourcePattern& resource = privilege.getResourcePattern(); - if (resource.isDatabasePattern() || resource.isExactNamespacePattern()) { - if (!isAuthorizedForActionsOnResource( - ResourcePattern::forDatabaseName(resource.databaseToMatch()), - ActionType::revokeRole)) { - return Status(ErrorCodes::Unauthorized, - str::stream() << "Not authorized to revoke privileges on the " - << resource.databaseToMatch() << "database"); - } - } else if (!isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName("admin"), - ActionType::revokeRole)) { - return Status(ErrorCodes::Unauthorized, - "To revoke privileges affecting multiple databases or the cluster," - " must be authorized to revoke roles from the admin database"); - } - return Status::OK(); -} - bool AuthorizationSessionImpl::isAuthorizedToParseNamespaceElement(const BSONElement& element) { const bool isUUID = element.type() == BinData && element.binDataType() == BinDataType::newUUID; @@ -586,16 +283,6 @@ bool AuthorizationSessionImpl::isAuthorizedToCreateRole(const RoleName& roleName return false; } -bool AuthorizationSessionImpl::isAuthorizedToGrantRole(const RoleName& role) { - return isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(role.getDB()), - ActionType::grantRole); -} - -bool AuthorizationSessionImpl::isAuthorizedToRevokeRole(const RoleName& role) { - return isAuthorizedForActionsOnResource(ResourcePattern::forDatabaseName(role.getDB()), - ActionType::revokeRole); -} - bool AuthorizationSessionImpl::isAuthorizedForPrivilege(const Privilege& privilege) { if (_externalState->shouldIgnoreAuthChecks()) return true; @@ -717,16 +404,6 @@ bool AuthorizationSessionImpl::isAuthorizedToChangeAsUser(const UserName& userNa return actions.contains(actionType); } -bool AuthorizationSessionImpl::isAuthorizedToChangeOwnPasswordAsUser(const UserName& userName) { - return AuthorizationSessionImpl::isAuthorizedToChangeAsUser(userName, - ActionType::changeOwnPassword); -} - -bool AuthorizationSessionImpl::isAuthorizedToChangeOwnCustomDataAsUser(const UserName& userName) { - return AuthorizationSessionImpl::isAuthorizedToChangeAsUser(userName, - ActionType::changeOwnCustomData); -} - StatusWith<PrivilegeVector> AuthorizationSessionImpl::checkAuthorizedToListCollections( StringData dbname, const BSONObj& cmdObj) { if (cmdObj["authorizedCollections"].trueValue() && cmdObj["nameOnly"].trueValue() && @@ -753,6 +430,10 @@ bool AuthorizationSessionImpl::isAuthenticatedAsUserWithRole(const RoleName& rol return false; } +bool AuthorizationSessionImpl::shouldIgnoreAuthChecks() { + return _externalState->shouldIgnoreAuthChecks(); +} + bool AuthorizationSessionImpl::isAuthenticated() { return _authenticatedUsers.begin() != _authenticatedUsers.end(); } |