summaryrefslogtreecommitdiff
path: root/src/mongo/db/catalog
diff options
context:
space:
mode:
authorNick Zolnierz <nicholas.zolnierz@mongodb.com>2020-04-09 10:45:06 -0400
committerEvergreen Agent <no-reply@evergreen.mongodb.com>2020-04-27 13:47:14 +0000
commit3032eb8c2a10163bf727767efe2b73b8d60c9ecb (patch)
tree5e57608a4b1ee76158d2706c1dac6d1ce9e653e2 /src/mongo/db/catalog
parentb3ecf8bc6b2c787d672be1c31187f3f2b2d673cd (diff)
downloadmongo-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.cpp110
-rw-r--r--src/mongo/db/catalog/collection.h2
-rw-r--r--src/mongo/db/catalog/collection_impl.cpp67
-rw-r--r--src/mongo/db/catalog/collection_impl.h2
-rw-r--r--src/mongo/db/catalog/collection_mock.h2
-rw-r--r--src/mongo/db/catalog/database_impl.cpp13
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.