Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion source/common/config/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,10 @@ class Utility {
Protobuf::RepeatedPtrField<ResourceType> typed_resources;
for (const auto& resource : response.resources()) {
auto* typed_resource = typed_resources.Add();
resource.UnpackTo(typed_resource);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: we were ignoring mismatched type errors during unpacking.

if (!resource.UnpackTo(typed_resource)) {
throw EnvoyException("Unable to unpack " + resource.DebugString());
}
MessageUtil::checkUnknownFields(*typed_resource);
}
return typed_resources;
}
Expand Down
14 changes: 12 additions & 2 deletions source/common/protobuf/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,18 @@ ProtoValidationException::ProtoValidationException(const std::string& validation
ENVOY_LOG_MISC(debug, "Proto validation error; throwing {}", what());
}

ProtoUnknownFieldsMode MessageUtil::proto_unknown_fields = ProtoUnknownFieldsMode::Strict;

void MessageUtil::loadFromJson(const std::string& json, Protobuf::Message& message) {
MessageUtil::loadFromJsonEx(json, message, ProtoUnknownFieldsMode::Strict);
}

void MessageUtil::loadFromJsonEx(const std::string& json, Protobuf::Message& message,
ProtoUnknownFieldsMode proto_unknown_fields) {
Protobuf::util::JsonParseOptions options;
options.ignore_unknown_fields = true;
if (proto_unknown_fields == ProtoUnknownFieldsMode::Allow) {
options.ignore_unknown_fields = true;
}
const auto status = Protobuf::util::JsonStringToMessage(json, &message, options);
if (!status.ok()) {
throw EnvoyException("Unable to parse JSON as proto (" + status.ToString() + "): " + json);
Expand All @@ -68,6 +77,7 @@ void MessageUtil::loadFromFile(const std::string& path, Protobuf::Message& messa
if (StringUtil::endsWith(path, ".pb")) {
// Attempt to parse the binary format.
if (message.ParseFromString(contents)) {
MessageUtil::checkUnknownFields(message);
return;
}
throw EnvoyException("Unable to parse file \"" + path + "\" as a binary protobuf (type " +
Expand Down Expand Up @@ -121,7 +131,7 @@ void MessageUtil::jsonConvert(const Protobuf::Message& source, Protobuf::Message
throw EnvoyException(fmt::format("Unable to convert protobuf message to JSON string: {} {}",
status.ToString(), source.DebugString()));
}
MessageUtil::loadFromJson(json, dest);
MessageUtil::loadFromJsonEx(json, dest, MessageUtil::proto_unknown_fields);
}

ProtobufWkt::Struct MessageUtil::keyValueStruct(const std::string& key, const std::string& value) {
Expand Down
15 changes: 15 additions & 0 deletions source/common/protobuf/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ class ProtoValidationException : public EnvoyException {
ProtoValidationException(const std::string& validation_error, const Protobuf::Message& message);
};

enum class ProtoUnknownFieldsMode { Strict, Allow };

class MessageUtil {
public:
// std::hash
Expand All @@ -158,7 +160,19 @@ class MessageUtil {
return HashUtil::xxHash64(text);
}

static ProtoUnknownFieldsMode proto_unknown_fields;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: For readability, can you do an explicit initialization to Strict here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you give me an example how to initialize a non-const static in the header?
I have explicit initialization in the .cc file.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, missed that, LGTM, thanks.


static void checkUnknownFields(const Protobuf::Message& message) {
if (MessageUtil::proto_unknown_fields == ProtoUnknownFieldsMode::Strict &&
!message.GetReflection()->GetUnknownFields(message).empty()) {
throw EnvoyException("Protobuf message (type " + message.GetTypeName() +
") has unknown fields");
}
}

static void loadFromJson(const std::string& json, Protobuf::Message& message);
static void loadFromJsonEx(const std::string& json, Protobuf::Message& message,
ProtoUnknownFieldsMode proto_unknown_fields);
static void loadFromYaml(const std::string& yaml, Protobuf::Message& message);
static void loadFromFile(const std::string& path, Protobuf::Message& message);

Expand Down Expand Up @@ -214,6 +228,7 @@ class MessageUtil {
if (!message.UnpackTo(&typed_message)) {
throw EnvoyException("Unable to unpack " + message.DebugString());
}
checkUnknownFields(typed_message);
return typed_message;
};

Expand Down
1 change: 1 addition & 0 deletions source/server/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ envoy_cc_library(
"//include/envoy/stats:stats_interface",
"//source/common/common:macros",
"//source/common/common:version_lib",
"//source/common/protobuf:utility_lib",
"//source/common/stats:stats_lib",
],
)
Expand Down
7 changes: 7 additions & 0 deletions source/server/options_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "common/common/logger.h"
#include "common/common/macros.h"
#include "common/common/version.h"
#include "common/protobuf/utility.h"

#include "spdlog/spdlog.h"
#include "tclap/CmdLine.h"
Expand Down Expand Up @@ -64,6 +65,9 @@ OptionsImpl::OptionsImpl(int argc, const char* const* argv,
TCLAP::SwitchArg allow_v1_config("", "allow-deprecated-v1-api", "allow use of legacy v1 config",
cmd, false);

TCLAP::SwitchArg allow_unknown_fields("", "allow-unknown-fields",
"allow unknown fields in the configuration", cmd, false);

TCLAP::ValueArg<std::string> admin_address_path("", "admin-address-path", "Admin address path",
false, "", "string", cmd);
TCLAP::ValueArg<std::string> local_address_ip_version("", "local-address-ip-version",
Expand Down Expand Up @@ -184,6 +188,9 @@ OptionsImpl::OptionsImpl(int argc, const char* const* argv,
config_path_ = config_path.getValue();
config_yaml_ = config_yaml.getValue();
v2_config_only_ = !allow_v1_config.getValue();
if (allow_unknown_fields.getValue()) {
MessageUtil::proto_unknown_fields = ProtoUnknownFieldsMode::Allow;
}
admin_address_path_ = admin_address_path.getValue();
log_path_ = log_path.getValue();
restart_epoch_ = restart_epoch.getValue();
Expand Down
10 changes: 10 additions & 0 deletions test/common/config/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,16 @@ TEST(UtilityTest, GetTypedResources) {
EXPECT_EQ("1", typed_resources[1].cluster_name());
}

TEST(UtilityTest, GetTypedResourcesWrongType) {
envoy::api::v2::DiscoveryResponse response;
envoy::api::v2::ClusterLoadAssignment load_assignment_0;
load_assignment_0.set_cluster_name("0");
response.add_resources()->PackFrom(load_assignment_0);

EXPECT_THROW_WITH_REGEX(Utility::getTypedResources<envoy::api::v2::Listener>(response),
EnvoyException, "Unable to unpack .*");
}

TEST(UtilityTest, ComputeHashedVersion) {
EXPECT_EQ("hash_2e1472b57af294d1", Utility::computeHashedVersion("{}").first);
EXPECT_EQ("hash_33bf00a859c4ba3f", Utility::computeHashedVersion("foo").first);
Expand Down
32 changes: 32 additions & 0 deletions test/common/protobuf/utility_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,17 @@ TEST(UtilityTest, LoadBinaryProtoFromFile) {
EXPECT_TRUE(TestUtility::protoEqual(bootstrap, proto_from_file));
}

TEST(UtilityTest, LoadBinaryProtoUnknownFieldFromFile) {
ProtobufWkt::Duration source_duration;
source_duration.set_seconds(42);
const std::string filename =
TestEnvironment::writeStringToFileForTest("proto.pb", source_duration.SerializeAsString());
envoy::config::bootstrap::v2::Bootstrap proto_from_file;
EXPECT_THROW_WITH_MESSAGE(
MessageUtil::loadFromFile(filename, proto_from_file), EnvoyException,
"Protobuf message (type envoy.config.bootstrap.v2.Bootstrap) has unknown fields");
}

TEST(UtilityTest, LoadTextProtoFromFile) {
envoy::config::bootstrap::v2::Bootstrap bootstrap;
bootstrap.mutable_cluster_manager()
Expand Down Expand Up @@ -212,6 +223,25 @@ TEST(UtilityTest, HashedValueStdHash) {
EXPECT_NE(set.find(hv3), set.end());
}

TEST(UtilityTest, AnyConvertWrongType) {
ProtobufWkt::Duration source_duration;
source_duration.set_seconds(42);
ProtobufWkt::Any source_any;
source_any.PackFrom(source_duration);
EXPECT_THROW_WITH_REGEX(MessageUtil::anyConvert<ProtobufWkt::Timestamp>(source_any),
EnvoyException, "Unable to unpack .*");
}

TEST(UtilityTest, AnyConvertWrongFields) {
const ProtobufWkt::Struct obj = MessageUtil::keyValueStruct("test_key", "test_value");
ProtobufWkt::Any source_any;
source_any.PackFrom(obj);
source_any.set_type_url("type.google.com/google.protobuf.Timestamp");
EXPECT_THROW_WITH_MESSAGE(MessageUtil::anyConvert<ProtobufWkt::Timestamp>(source_any),
EnvoyException,
"Protobuf message (type google.protobuf.Timestamp) has unknown fields");
}

TEST(UtilityTest, JsonConvertSuccess) {
ProtobufWkt::Duration source_duration;
source_duration.set_seconds(42);
Expand All @@ -221,9 +251,11 @@ TEST(UtilityTest, JsonConvertSuccess) {
}

TEST(UtilityTest, JsonConvertUnknownFieldSuccess) {
MessageUtil::proto_unknown_fields = ProtoUnknownFieldsMode::Allow;
const ProtobufWkt::Struct obj = MessageUtil::keyValueStruct("test_key", "test_value");
envoy::config::bootstrap::v2::Bootstrap bootstrap;
EXPECT_NO_THROW(MessageUtil::jsonConvert(obj, bootstrap));
MessageUtil::proto_unknown_fields = ProtoUnknownFieldsMode::Strict;
}

TEST(UtilityTest, JsonConvertFail) {
Expand Down
5 changes: 4 additions & 1 deletion test/common/router/config_impl_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4490,6 +4490,9 @@ class PerFilterConfigsTest : public testing::Test {
Server::Configuration::FactoryContext&) override {
NOT_IMPLEMENTED_GCOVR_EXCL_LINE;
}
ProtobufTypes::MessagePtr createEmptyRouteConfigProto() override {
return ProtobufTypes::MessagePtr{new ProtobufWkt::Timestamp()};
}
};

void checkEach(const std::string& yaml, uint32_t expected_entry, uint32_t expected_route,
Expand Down Expand Up @@ -4563,7 +4566,7 @@ name: foo
routes:
- match: { prefix: "/" }
route: { cluster: baz }
per_filter_config: { test.default.filter: { unknown_key: 123} }
per_filter_config: { test.default.filter: { seconds: 123} }
)EOF";

checkNoPerFilterConfig(yaml);
Expand Down
1 change: 0 additions & 1 deletion test/config/utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ const std::string ConfigHelper::DEFAULT_HEALTH_CHECK_FILTER =
name: envoy.health_check
config:
pass_through_mode: false
endpoint: /healthcheck
)EOF";

const std::string ConfigHelper::DEFAULT_SQUASH_FILTER =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,7 @@ class ThriftConnManagerIntegrationTest
route_config:
name: "routes"
routes:
- match:
method_name: "execute"
route:
cluster: "cluster_0"
- match:
method_name: "poke"
- match: {}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops. Could you revert the original and switch method_name to method? That should eliminate the unknown fields. (I'll circle back and add a second cluster to this test and use it for the one of the routes.)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, that'll just make half the tests fail. You can leave this and I'll fix it myself.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I am getting errors if I put it back...

route:
cluster: "cluster_0"
)EOF";
Expand Down
1 change: 0 additions & 1 deletion test/integration/http_integration.cc
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,6 @@ void HttpIntegrationTest::testComputedHealthCheck() {
name: envoy.health_check
config:
pass_through_mode: false
endpoint: /healthcheck
cluster_min_healthy_percentages:
example_cluster_name: { value: 75 }
)EOF");
Expand Down
2 changes: 1 addition & 1 deletion tools/protodoc/protodoc.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ def _proto_doc_aspect_impl(target, ctx):
return [OutputGroupInfo(rst = transitive_outputs)]

proto_doc_aspect = aspect(
implementation = _proto_doc_aspect_impl,
attr_aspects = ["deps"],
attrs = {
"_protoc": attr.label(
Expand All @@ -92,4 +91,5 @@ proto_doc_aspect = aspect(
cfg = "host",
),
},
implementation = _proto_doc_aspect_impl,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is just some Buildifier artifact..

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, not sure why check_format is eager to refactor all bazel files on my machine.

)