From 26ea3d0d44d791ada367b2c2525ba6ac592ecdcf Mon Sep 17 00:00:00 2001 From: Shreyas Kalyan Date: Thu, 24 Mar 2022 15:15:05 -0400 Subject: SERVER-64294 Enhance redact to always redact BinData 6 --- src/mongo/bson/bsonobj.cpp | 36 ++++++++++++++----- src/mongo/bson/bsonobj.h | 2 +- src/mongo/logv2/SConscript | 12 +++++++ src/mongo/logv2/log_util.cpp | 10 ++++++ src/mongo/logv2/log_util.h | 11 ++++++ src/mongo/logv2/logv2_options.cpp | 71 ++++++++++++++++++++++++++++++++++++++ src/mongo/logv2/logv2_options.idl | 41 ++++++++++++++++++++++ src/mongo/logv2/redaction.cpp | 8 +++-- src/mongo/logv2/redaction_test.cpp | 27 +++++++++++++++ 9 files changed, 206 insertions(+), 12 deletions(-) create mode 100644 src/mongo/logv2/logv2_options.cpp create mode 100644 src/mongo/logv2/logv2_options.idl diff --git a/src/mongo/bson/bsonobj.cpp b/src/mongo/bson/bsonobj.cpp index 73f824735ed..7b5fa7dae04 100644 --- a/src/mongo/bson/bsonobj.cpp +++ b/src/mongo/bson/bsonobj.cpp @@ -27,6 +27,7 @@ * it in the license file. */ +#include "mongo/bson/bsonelement.h" #define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kDefault #include "mongo/db/jsobj.h" @@ -137,25 +138,42 @@ BSONObj BSONObj::getOwned(const BSONObj& obj) { return obj.getOwned(); } -BSONObj BSONObj::redact() const { +BSONObj BSONObj::redact(bool onlyEncryptedFields) const { _validateUnownedSize(objsize()); // Helper to get an "internal function" to be able to do recursion struct redactor { - void operator()(BSONObjBuilder& builder, const BSONObj& obj, bool appendMask) { + void appendRedactedElem(BSONObjBuilder& builder, const BSONElement& e, bool appendMask) { + if (appendMask) { + builder.append(e.fieldNameStringData(), "###"_sd); + } else { + builder.appendNull(e.fieldNameStringData()); + } + } + + void operator()(BSONObjBuilder& builder, + const BSONObj& obj, + bool appendMask, + bool onlyEncryptedFields) { for (BSONElement e : obj) { if (e.type() == Object) { BSONObjBuilder subBuilder = builder.subobjStart(e.fieldNameStringData()); - operator()(subBuilder, e.Obj(), appendMask); + operator()(subBuilder, e.Obj(), appendMask, onlyEncryptedFields); subBuilder.done(); } else if (e.type() == Array) { BSONObjBuilder subBuilder = builder.subarrayStart(e.fieldNameStringData()); - operator()(subBuilder, e.Obj(), appendMask); + operator()(subBuilder, e.Obj(), appendMask, onlyEncryptedFields); subBuilder.done(); - } else if (appendMask) { - builder.append(e.fieldNameStringData(), "###"_sd); } else { - builder.appendNull(e.fieldNameStringData()); + if (onlyEncryptedFields) { + if (e.type() == BinData && e.binDataType() == BinDataType::Encrypt) { + appendRedactedElem(builder, e, appendMask); + } else { + builder.append(e); + } + } else { + appendRedactedElem(builder, e, appendMask); + } } } } @@ -163,7 +181,7 @@ BSONObj BSONObj::redact() const { try { BSONObjBuilder builder; - redactor()(builder, *this, /*appendMask=*/true); + redactor()(builder, *this, /*appendMask=*/true, onlyEncryptedFields); return builder.obj(); } catch (const ExceptionFor&) { } @@ -173,7 +191,7 @@ BSONObj BSONObj::redact() const { // we use BSONType::jstNull, which ensures the redacted object will not be larger than the // original. BSONObjBuilder builder; - redactor()(builder, *this, /*appendMask=*/false); + redactor()(builder, *this, /*appendMask=*/false, onlyEncryptedFields); return builder.obj(); } diff --git a/src/mongo/bson/bsonobj.h b/src/mongo/bson/bsonobj.h index 2f1e511704d..a5270adfd25 100644 --- a/src/mongo/bson/bsonobj.h +++ b/src/mongo/bson/bsonobj.h @@ -267,7 +267,7 @@ public: /** * @return a new full (and owned) redacted copy of the object. */ - BSONObj redact() const; + BSONObj redact(bool onlyEncryptedFields = false) const; /** * Readable representation of a BSON object in an extended JSON-style notation. diff --git a/src/mongo/logv2/SConscript b/src/mongo/logv2/SConscript index 2ceefd5b03c..05351e1a2fe 100644 --- a/src/mongo/logv2/SConscript +++ b/src/mongo/logv2/SConscript @@ -27,3 +27,15 @@ env.Benchmark( '$BUILD_DIR/mongo/base', ], ) + +env.Library( + target='logv2_options', + source=[ + 'logv2_options.cpp', + 'logv2_options.idl', + ], + LIBDEPS_PRIVATE=[ + '$BUILD_DIR/mongo/base', + '$BUILD_DIR/mongo/idl/server_parameter', + ], +) diff --git a/src/mongo/logv2/log_util.cpp b/src/mongo/logv2/log_util.cpp index c44ff5988af..8013d8a328e 100644 --- a/src/mongo/logv2/log_util.cpp +++ b/src/mongo/logv2/log_util.cpp @@ -42,6 +42,7 @@ namespace mongo::logv2 { namespace { AtomicWord redactionEnabled{false}; +AtomicWord redactBinDataEncrypt{true}; std::map logRotateCallbacks; } // namespace @@ -101,4 +102,13 @@ bool shouldRedactLogs() { void setShouldRedactLogs(bool enabled) { redactionEnabled.store(enabled); } + +bool shouldRedactBinDataEncrypt() { + return redactBinDataEncrypt.loadRelaxed(); +} + +void setShouldRedactBinDataEncrypt(bool enabled) { + redactBinDataEncrypt.store(enabled); +} + } // namespace mongo::logv2 diff --git a/src/mongo/logv2/log_util.h b/src/mongo/logv2/log_util.h index e1b6c2c34e0..09505e02d94 100644 --- a/src/mongo/logv2/log_util.h +++ b/src/mongo/logv2/log_util.h @@ -90,4 +90,15 @@ bool shouldRedactLogs(); * Set the 'redact' mode of the server. */ void setShouldRedactLogs(bool enabled); + +/** + * Returns true if the BinData Encrypt should be redacted. Default true. + */ +bool shouldRedactBinDataEncrypt(); + +/** + * Sets the redact mode of the bin data encrypt field. + */ +void setShouldRedactBinDataEncrypt(bool enabled); + } // namespace mongo::logv2 diff --git a/src/mongo/logv2/logv2_options.cpp b/src/mongo/logv2/logv2_options.cpp new file mode 100644 index 00000000000..05b786e8878 --- /dev/null +++ b/src/mongo/logv2/logv2_options.cpp @@ -0,0 +1,71 @@ +/** + * Copyright (C) 2022-present MongoDB, Inc. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the Server Side Public License, version 1, + * as published by MongoDB, Inc. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * Server Side Public License for more details. + * + * You should have received a copy of the Server Side Public License + * along with this program. If not, see + * . + * + * As a special exception, the copyright holders give permission to link the + * code of portions of this program with the OpenSSL library under certain + * conditions as described in each individual source file and distribute + * linked combinations including the program with the OpenSSL library. You + * must comply with the Server Side Public License in all respects for + * all of the code used other than as permitted herein. If you modify file(s) + * with this exception, you may extend this exception to your version of the + * file(s), but you are not obligated to do so. If you do not wish to do so, + * delete this exception statement from your version. If you delete this + * exception statement from all source files in the program, then also delete + * it in the license file. + */ +#define MONGO_LOGV2_DEFAULT_COMPONENT ::mongo::logv2::LogComponent::kControl + +#include "mongo/platform/basic.h" + +#include "mongo/base/status.h" +#include "mongo/logv2/log_util.h" +#include "mongo/logv2/logv2_options_gen.h" +#include "mongo/util/options_parser/option_section.h" +#include "mongo/util/options_parser/startup_option_init.h" +#include "mongo/util/options_parser/startup_options.h" + +namespace mongo { + +void RedactEncryptedFields::append(OperationContext* opCtx, + BSONObjBuilder& b, + const std::string& name) { + b << name << logv2::shouldRedactBinDataEncrypt(); +} + +Status RedactEncryptedFields::set(const BSONElement& newValueElement) { + bool newVal; + if (!newValueElement.coerce(&newVal)) { + return {ErrorCodes::BadValue, + str::stream() << "Invalid value for redactEncryptedFields: " << newValueElement}; + } + + logv2::setShouldRedactBinDataEncrypt(newVal); + return Status::OK(); +} + +Status RedactEncryptedFields::setFromString(const std::string& str) { + if (str == "true" || str == "1") { + logv2::setShouldRedactBinDataEncrypt(true); + } else if (str == "false" || str == "0") { + logv2::setShouldRedactBinDataEncrypt(false); + } else { + return {ErrorCodes::BadValue, + str::stream() << "Invalid value for redactEncryptedFields: " << str}; + } + return Status::OK(); +} + +} // namespace mongo diff --git a/src/mongo/logv2/logv2_options.idl b/src/mongo/logv2/logv2_options.idl new file mode 100644 index 00000000000..f385e0c5f07 --- /dev/null +++ b/src/mongo/logv2/logv2_options.idl @@ -0,0 +1,41 @@ +# Copyright (C) 2022-present MongoDB, Inc. +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the Server Side Public License, version 1, +# as published by MongoDB, Inc. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# Server Side Public License for more details. +# +# You should have received a copy of the Server Side Public License +# along with this program. If not, see +# . +# +# As a special exception, the copyright holders give permission to link the +# code of portions of this program with the OpenSSL library under certain +# conditions as described in each individual source file and distribute +# linked combinations including the program with the OpenSSL library. You +# must comply with the Server Side Public License in all respects for +# all of the code used other than as permitted herein. If you modify file(s) +# with this exception, you may extend this exception to your version of the +# file(s), but you are not obligated to do so. If you do not wish to do so, +# delete this exception statement from your version. If you delete this +# exception statement from all source files in the program, then also delete +# it in the license file. +# + +global: + cpp_namespace: "mongo" + +imports: + - "mongo/idl/basic_types.idl" + +server_parameters: + redactEncryptedFields: + description: "Enables redaction of encrypted fields in BSON Objects, defaults to on" + set_at: [ startup, runtime ] + cpp_class: + name: RedactEncryptedFields + override_set: true diff --git a/src/mongo/logv2/redaction.cpp b/src/mongo/logv2/redaction.cpp index 239e091aead..0b143ba39ae 100644 --- a/src/mongo/logv2/redaction.cpp +++ b/src/mongo/logv2/redaction.cpp @@ -36,6 +36,7 @@ #include "mongo/base/status.h" #include "mongo/bson/bsonobj.h" #include "mongo/logv2/log_util.h" +#include "mongo/logv2/logv2_options_gen.h" #include "mongo/util/assert_util.h" namespace mongo { @@ -48,10 +49,13 @@ constexpr auto kRedactionDefaultMask = "###"_sd; BSONObj redact(const BSONObj& objectToRedact) { if (!logv2::shouldRedactLogs()) { - return objectToRedact; + if (!logv2::shouldRedactBinDataEncrypt()) { + return objectToRedact; + } + return objectToRedact.redact(true /* onlyEncryptedFields */); } - return objectToRedact.redact(); + return objectToRedact.redact(false /* onlyEncryptedFields */); } StringData redact(StringData stringToRedact) { diff --git a/src/mongo/logv2/redaction_test.cpp b/src/mongo/logv2/redaction_test.cpp index bc22f60b4ae..dda840a4681 100644 --- a/src/mongo/logv2/redaction_test.cpp +++ b/src/mongo/logv2/redaction_test.cpp @@ -31,6 +31,9 @@ #include "mongo/logv2/redaction.h" +#include "mongo/base/error_extra_info.h" +#include "mongo/bson/bsonobjbuilder.h" +#include "mongo/bson/bsontypes.h" #include "mongo/db/jsobj.h" #include "mongo/logv2/log_util.h" #include "mongo/unittest/unittest.h" @@ -122,6 +125,30 @@ TEST(RedactBSONTest, BasicBSON) { "{ a: \"###\", a: \"###\" }")}); } +unsigned char zero[16] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; + +TEST(RedactEncryptedStringTest, BasicStrings) { + logv2::setShouldRedactBinDataEncrypt(true); + logv2::setShouldRedactLogs(false); + + BSONObjBuilder builder{}; + builder.appendBinData("type6", sizeof(zero), BinDataType::Encrypt, zero); + builder.append("string", "string"); + { + BSONObjBuilder sub(builder.subobjStart("nestedobj")); + sub.appendBinData("subobj", sizeof(zero), BinDataType::Encrypt, zero); + } + BSONObj obj = builder.done(); + + std::cout << "This is obj: " << obj.toString() << std::endl; + + auto redactedStr = R"({ type6: "###", string: "string", nestedobj: { subobj: "###" } })"; + ASSERT_EQ(redact(obj).toString(), redactedStr); + + logv2::setShouldRedactBinDataEncrypt(false); + ASSERT_EQ(redact(obj).toString(), obj.toString()); +} + void testBSONCases(std::vector& testCases) { for (auto m : testCases) { ASSERT_EQ(redact(m.first).toString(), m.second); -- cgit v1.2.1