diff options
author | Nick Zolnierz <nicholas.zolnierz@mongodb.com> | 2020-04-09 10:45:06 -0400 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2020-04-27 13:47:14 +0000 |
commit | 3032eb8c2a10163bf727767efe2b73b8d60c9ecb (patch) | |
tree | 5e57608a4b1ee76158d2706c1dac6d1ce9e653e2 /src/mongo/db/catalog | |
parent | b3ecf8bc6b2c787d672be1c31187f3f2b2d673cd (diff) | |
download | mongo-3032eb8c2a10163bf727767efe2b73b8d60c9ecb.tar.gz |
SERVER-45514 Reject document validators with encryption-related keywords if the validationAction is "warn" or validationLevel is "moderate"
Diffstat (limited to 'src/mongo/db/catalog')
-rw-r--r-- | src/mongo/db/catalog/coll_mod.cpp | 110 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection.h | 2 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.cpp | 67 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_impl.h | 2 | ||||
-rw-r--r-- | src/mongo/db/catalog/collection_mock.h | 2 | ||||
-rw-r--r-- | src/mongo/db/catalog/database_impl.cpp | 13 |
6 files changed, 115 insertions, 81 deletions
diff --git a/src/mongo/db/catalog/coll_mod.cpp b/src/mongo/db/catalog/coll_mod.cpp index da5e601bdef..40d6b541f97 100644 --- a/src/mongo/db/catalog/coll_mod.cpp +++ b/src/mongo/db/catalog/coll_mod.cpp @@ -72,9 +72,9 @@ struct CollModRequest { BSONElement indexHidden = {}; BSONElement viewPipeLine = {}; std::string viewOn = {}; - BSONElement collValidator = {}; - std::string collValidationAction = {}; - std::string collValidationLevel = {}; + boost::optional<Collection::Validator> collValidator; + boost::optional<std::string> collValidationAction; + boost::optional<std::string> collValidationLevel; bool recordPreImages = false; }; @@ -191,15 +191,13 @@ StatusWith<CollModRequest> parseCollModRequest(OperationContext* opCtx, ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo46) { maxFeatureCompatibilityVersion = currentFCV; } - auto statusW = coll->parseValidator(opCtx, - e.Obj(), - MatchExpressionParser::kDefaultSpecialFeatures, - maxFeatureCompatibilityVersion); - if (!statusW.isOK()) { - return statusW.getStatus(); + cmr.collValidator = coll->parseValidator(opCtx, + e.Obj().getOwned(), + MatchExpressionParser::kDefaultSpecialFeatures, + maxFeatureCompatibilityVersion); + if (!cmr.collValidator->isOK()) { + return cmr.collValidator->getStatus(); } - - cmr.collValidator = e; } else if (fieldName == "validationLevel" && !isView) { auto status = coll->parseValidationLevel(e.String()); if (!status.isOK()) @@ -345,19 +343,22 @@ Status _collModInternal(OperationContext* opCtx, auto oplogEntryObj = oplogEntryBuilder.obj(); // Save both states of the CollModRequest to allow writeConflictRetries. - const CollModRequest cmrOld = statusW.getValue(); - CollModRequest cmrNew = statusW.getValue(); - - if (!cmrOld.indexHidden.eoo()) { - + CollModRequest cmrNew = std::move(statusW.getValue()); + auto viewPipeline = cmrNew.viewPipeLine; + auto viewOn = cmrNew.viewOn; + auto indexExpireAfterSeconds = cmrNew.indexExpireAfterSeconds; + auto indexHidden = cmrNew.indexHidden; + auto idx = cmrNew.idx; + + if (indexHidden) { if (serverGlobalParams.featureCompatibility.getVersion() < ServerGlobalParams::FeatureCompatibility::Version::kFullyUpgradedTo46 && - cmrOld.indexHidden.booleanSafe()) { + indexHidden.booleanSafe()) { return Status(ErrorCodes::BadValue, "Hidden indexes can only be created with FCV 4.6"); } if (coll->ns().isSystem()) return Status(ErrorCodes::BadValue, "Can't hide index on system collection"); - if (cmrOld.idx->isIdIndex()) + if (idx->isIdIndex()) return Status(ErrorCodes::BadValue, "can't hide _id index"); } @@ -367,11 +368,11 @@ Status _collModInternal(OperationContext* opCtx, // Handle collMod on a view and return early. The View Catalog handles the creation of oplog // entries for modifications on a view. if (view) { - if (!cmrOld.viewPipeLine.eoo()) - view->setPipeline(cmrOld.viewPipeLine); + if (viewPipeline) + view->setPipeline(viewPipeline); - if (!cmrOld.viewOn.empty()) - view->setViewOn(NamespaceString(dbName, cmrOld.viewOn)); + if (!viewOn.empty()) + view->setViewOn(NamespaceString(dbName, viewOn)); ViewCatalog* catalog = ViewCatalog::get(db); @@ -401,53 +402,51 @@ Status _collModInternal(OperationContext* opCtx, // Handle collMod operation type appropriately. - if (!cmrOld.indexExpireAfterSeconds.eoo() || !cmrOld.indexHidden.eoo()) { + if (indexExpireAfterSeconds || indexHidden) { BSONElement newExpireSecs = {}; BSONElement oldExpireSecs = {}; BSONElement newHidden = {}; BSONElement oldHidden = {}; + // TTL Index - if (!cmrOld.indexExpireAfterSeconds.eoo()) { - newExpireSecs = cmrOld.indexExpireAfterSeconds; - oldExpireSecs = cmrOld.idx->infoObj().getField("expireAfterSeconds"); + if (indexExpireAfterSeconds) { + newExpireSecs = indexExpireAfterSeconds; + oldExpireSecs = idx->infoObj().getField("expireAfterSeconds"); if (SimpleBSONElementComparator::kInstance.evaluate(oldExpireSecs != newExpireSecs)) { // Change the value of "expireAfterSeconds" on disk. DurableCatalog::get(opCtx)->updateTTLSetting(opCtx, coll->getCatalogId(), - cmrOld.idx->indexName(), + idx->indexName(), newExpireSecs.safeNumberLong()); } } // User wants to hide or unhide index. - if (!cmrOld.indexHidden.eoo()) { - newHidden = cmrOld.indexHidden; - oldHidden = cmrOld.idx->infoObj().getField("hidden"); + if (indexHidden) { + newHidden = indexHidden; + oldHidden = idx->infoObj().getField("hidden"); // Make sure when we set 'hidden' to false, we can remove the hidden field from // catalog. if (SimpleBSONElementComparator::kInstance.evaluate(oldHidden != newHidden)) { - DurableCatalog::get(opCtx)->updateHiddenSetting(opCtx, - coll->getCatalogId(), - cmrOld.idx->indexName(), - newHidden.booleanSafe()); + DurableCatalog::get(opCtx)->updateHiddenSetting( + opCtx, coll->getCatalogId(), idx->indexName(), newHidden.booleanSafe()); } } - - indexCollModInfo = IndexCollModInfo{ - cmrOld.indexExpireAfterSeconds.eoo() ? boost::optional<Seconds>() - : Seconds(newExpireSecs.safeNumberLong()), - cmrOld.indexExpireAfterSeconds.eoo() ? boost::optional<Seconds>() - : Seconds(oldExpireSecs.safeNumberLong()), - cmrOld.indexHidden.eoo() ? boost::optional<bool>() : newHidden.booleanSafe(), - cmrOld.indexHidden.eoo() ? boost::optional<bool>() : oldHidden.booleanSafe(), - cmrNew.idx->indexName()}; + indexCollModInfo = + IndexCollModInfo{!indexExpireAfterSeconds ? boost::optional<Seconds>() + : Seconds(newExpireSecs.safeNumberLong()), + !indexExpireAfterSeconds ? boost::optional<Seconds>() + : Seconds(oldExpireSecs.safeNumberLong()), + !indexHidden ? boost::optional<bool>() : newHidden.booleanSafe(), + !indexHidden ? boost::optional<bool>() : oldHidden.booleanSafe(), + cmrNew.idx->indexName()}; // Notify the index catalog that the definition of this index changed. This will - // invalidate the idx pointer in cmrOld. On rollback of this WUOW, the idx pointer - // in cmrNew will be invalidated and the idx pointer in cmrOld will be valid again. - cmrNew.idx = coll->getIndexCatalog()->refreshEntry(opCtx, cmrOld.idx); + // invalidate the local idx pointer. On rollback of this WUOW, the idx pointer in + // cmrNew will be invalidated and the local var idx pointer will be valid again. + cmrNew.idx = coll->getIndexCatalog()->refreshEntry(opCtx, idx); opCtx->recoveryUnit()->registerChange(std::make_unique<CollModResultChange>( oldExpireSecs, newExpireSecs, oldHidden, newHidden, result)); @@ -457,13 +456,17 @@ Status _collModInternal(OperationContext* opCtx, } } - // The Validator, ValidationAction and ValidationLevel are already parsed and must be OK. - if (!cmrNew.collValidator.eoo()) - invariant(coll->setValidator(opCtx, cmrNew.collValidator.Obj())); - if (!cmrNew.collValidationAction.empty()) - invariant(coll->setValidationAction(opCtx, cmrNew.collValidationAction)); - if (!cmrNew.collValidationLevel.empty()) - invariant(coll->setValidationLevel(opCtx, cmrNew.collValidationLevel)); + if (cmrNew.collValidator) { + coll->setValidator(opCtx, std::move(*cmrNew.collValidator)); + } + if (cmrNew.collValidationAction) + uassertStatusOKWithContext( + coll->setValidationAction(opCtx, *cmrNew.collValidationAction), + "Failed to set validationAction"); + if (cmrNew.collValidationLevel) { + uassertStatusOKWithContext(coll->setValidationLevel(opCtx, *cmrNew.collValidationLevel), + "Failed to set validationLevel"); + } if (cmrNew.recordPreImages != oldCollOptions.recordPreImages) { coll->setRecordPreImages(opCtx, cmrNew.recordPreImages); @@ -471,7 +474,6 @@ Status _collModInternal(OperationContext* opCtx, // Only observe non-view collMods, as view operations are observed as operations on the // system.views collection. - auto* const opObserver = opCtx->getServiceContext()->getOpObserver(); opObserver->onCollMod( opCtx, nss, coll->uuid(), oplogEntryObj, oldCollOptions, indexCollModInfo); diff --git a/src/mongo/db/catalog/collection.h b/src/mongo/db/catalog/collection.h index 86deaf95b0a..eb33fe95582 100644 --- a/src/mongo/db/catalog/collection.h +++ b/src/mongo/db/catalog/collection.h @@ -417,7 +417,7 @@ public: * An empty validator removes all validation. * Requires an exclusive lock on the collection. */ - virtual Status setValidator(OperationContext* const opCtx, const BSONObj validator) = 0; + virtual void setValidator(OperationContext* const opCtx, Validator validator) = 0; virtual Status setValidationLevel(OperationContext* const opCtx, const StringData newLevel) = 0; virtual Status setValidationAction(OperationContext* const opCtx, diff --git a/src/mongo/db/catalog/collection_impl.cpp b/src/mongo/db/catalog/collection_impl.cpp index 13a66a8d960..d88ba0b767a 100644 --- a/src/mongo/db/catalog/collection_impl.cpp +++ b/src/mongo/db/catalog/collection_impl.cpp @@ -54,6 +54,7 @@ #include "mongo/db/index/index_access_method.h" #include "mongo/db/index/index_descriptor.h" #include "mongo/db/keypattern.h" +#include "mongo/db/matcher/expression_always_boolean.h" #include "mongo/db/matcher/expression_parser.h" #include "mongo/db/op_observer.h" #include "mongo/db/operation_context.h" @@ -284,6 +285,16 @@ void CollectionImpl::init(OperationContext* opCtx) { // Enforce that the validator can be used on this namespace. uassertStatusOK(checkValidatorCanBeUsedOnNs(validatorDoc, ns(), _uuid)); + + // Make sure to parse the action and level before the MatchExpression, since certain features + // are not supported with certain combinations of action and level. + _validationAction = uassertStatusOK(_parseValidationAction(collectionOptions.validationAction)); + _validationLevel = uassertStatusOK(_parseValidationLevel(collectionOptions.validationLevel)); + if (collectionOptions.recordPreImages) { + uassertStatusOK(validatePreImageRecording(opCtx, _ns)); + _recordPreImages = true; + } + // Store the result (OK / error) of parsing the validator, but do not enforce that the result is // OK. This is intentional, as users may have validators on disk which were considered well // formed in older versions but not in newer versions. @@ -298,12 +309,6 @@ void CollectionImpl::init(OperationContext* opCtx) { "namespace"_attr = _ns, "validatorStatus"_attr = _validator.getStatus()); } - _validationAction = uassertStatusOK(_parseValidationAction(collectionOptions.validationAction)); - _validationLevel = uassertStatusOK(_parseValidationLevel(collectionOptions.validationLevel)); - if (collectionOptions.recordPreImages) { - uassertStatusOK(validatePreImageRecording(opCtx, _ns)); - _recordPreImages = true; - } getIndexCatalog()->init(opCtx).transitional_ignore(); _initialized = true; @@ -420,6 +425,12 @@ Collection::Validator CollectionImpl::parseValidator( // validator to apply some additional checks. expCtx->isParsingCollectionValidator = true; + // If the validation action is "warn" or the level is "moderate", then disallow any encryption + // keywords. This is to prevent any plaintext data from showing up in the logs. + if (_validationAction == CollectionImpl::ValidationAction::WARN || + _validationLevel == CollectionImpl::ValidationLevel::MODERATE) + allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords; + auto statusWithMatcher = MatchExpressionParser::parse(validator, expCtx, ExtensionsCallbackNoop(), allowedFeatures); @@ -977,28 +988,19 @@ void CollectionImpl::cappedTruncateAfter(OperationContext* opCtx, RecordId end, _recordStore->cappedTruncateAfter(opCtx, end, inclusive); } -Status CollectionImpl::setValidator(OperationContext* opCtx, BSONObj validatorDoc) { +void CollectionImpl::setValidator(OperationContext* opCtx, Validator validator) { invariant(opCtx->lockState()->isCollectionLockedForMode(ns(), MODE_X)); - // Make owned early so that the parsed match expression refers to the owned object. - if (!validatorDoc.isOwned()) - validatorDoc = validatorDoc.getOwned(); - - // Note that, by the time we reach this, we should have already done a pre-parse that checks for - // banned features, so we don't need to include that check again. - auto newValidator = - parseValidator(opCtx, validatorDoc, MatchExpressionParser::kAllowAllSpecialFeatures); - if (!newValidator.isOK()) - return newValidator.getStatus(); - - DurableCatalog::get(opCtx)->updateValidator( - opCtx, getCatalogId(), validatorDoc, getValidationLevel(), getValidationAction()); + DurableCatalog::get(opCtx)->updateValidator(opCtx, + getCatalogId(), + validator.validatorDoc.getOwned(), + getValidationLevel(), + getValidationAction()); opCtx->recoveryUnit()->onRollback([this, oldValidator = std::move(_validator)]() mutable { this->_validator = std::move(oldValidator); }); - _validator = std::move(newValidator); - return Status::OK(); + _validator = std::move(validator); } StringData CollectionImpl::getValidationLevel() const { @@ -1034,6 +1036,17 @@ Status CollectionImpl::setValidationLevel(OperationContext* opCtx, StringData ne auto oldValidationLevel = _validationLevel; _validationLevel = levelSW.getValue(); + // If setting the level to 'moderate', then reparse the validator to verify that there aren't + // any incompatible keywords. + if (_validationLevel == CollectionImpl::ValidationLevel::MODERATE) { + auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures; + allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords; + auto validator = parseValidator(opCtx, _validator.validatorDoc, allowedFeatures); + if (!validator.isOK()) { + return validator.getStatus(); + } + } + DurableCatalog::get(opCtx)->updateValidator(opCtx, getCatalogId(), _validator.validatorDoc, @@ -1056,6 +1069,16 @@ Status CollectionImpl::setValidationAction(OperationContext* opCtx, StringData n auto oldValidationAction = _validationAction; _validationAction = actionSW.getValue(); + // If setting the action to 'warn', then reparse the validator to verify that there aren't any + // incompatible keywords. + if (_validationAction == CollectionImpl::ValidationAction::WARN) { + auto allowedFeatures = MatchExpressionParser::kAllowAllSpecialFeatures; + allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords; + auto validator = parseValidator(opCtx, _validator.validatorDoc, allowedFeatures); + if (!validator.isOK()) { + return validator.getStatus(); + } + } DurableCatalog::get(opCtx)->updateValidator(opCtx, getCatalogId(), diff --git a/src/mongo/db/catalog/collection_impl.h b/src/mongo/db/catalog/collection_impl.h index e3828f9471c..0d5926ec534 100644 --- a/src/mongo/db/catalog/collection_impl.h +++ b/src/mongo/db/catalog/collection_impl.h @@ -247,7 +247,7 @@ public: * An empty validator removes all validation. * Requires an exclusive lock on the collection. */ - Status setValidator(OperationContext* opCtx, BSONObj validator) final; + void setValidator(OperationContext* opCtx, Validator validator) final; Status setValidationLevel(OperationContext* opCtx, StringData newLevel) final; Status setValidationAction(OperationContext* opCtx, StringData newAction) final; diff --git a/src/mongo/db/catalog/collection_mock.h b/src/mongo/db/catalog/collection_mock.h index 9e3f97f1cc5..10672b977d4 100644 --- a/src/mongo/db/catalog/collection_mock.h +++ b/src/mongo/db/catalog/collection_mock.h @@ -178,7 +178,7 @@ public: std::abort(); } - Status setValidator(OperationContext* opCtx, BSONObj validator) { + void setValidator(OperationContext* opCtx, Validator validator) { std::abort(); } diff --git a/src/mongo/db/catalog/database_impl.cpp b/src/mongo/db/catalog/database_impl.cpp index c2a55d0734e..39884a9c931 100644 --- a/src/mongo/db/catalog/database_impl.cpp +++ b/src/mongo/db/catalog/database_impl.cpp @@ -924,8 +924,17 @@ Status DatabaseImpl::userCreateNS(OperationContext* opCtx, // validator to apply some additional checks. expCtx->isParsingCollectionValidator = true; - auto statusWithMatcher = - MatchExpressionParser::parse(collectionOptions.validator, std::move(expCtx)); + // If the validation action is "warn" or the level is "moderate", then disallow any + // encryption keywords. This is to prevent any plaintext data from showing up in the logs. + auto allowedFeatures = MatchExpressionParser::kDefaultSpecialFeatures; + if (collectionOptions.validationAction == "warn" || + collectionOptions.validationLevel == "moderate") + allowedFeatures &= ~MatchExpressionParser::AllowedFeatures::kEncryptKeywords; + + auto statusWithMatcher = MatchExpressionParser::parse(collectionOptions.validator, + std::move(expCtx), + ExtensionsCallbackNoop(), + allowedFeatures); // We check the status of the parse to see if there are any banned features, but we don't // actually need the result for now. |