Skip to content

Commit 9ed6292

Browse files
authored
pgv: validation support for Bootstrap/xDS/filters. (#2146)
This patch lands checking via https://github.com/lyft/protoc-gen-validate in Envoy for bootstrap, xDS ingestion and during filter instantiation. Risk Level: Medium (things might start blowing up on config ingestion that were slipping through before). Testing: Mostly negative tests for bootstrap/xDS/filters, since the happy path is already validated through the existing test suite. Signed-off-by: Harvey Tuch <htuch@google.com>
1 parent 99baa4e commit 9ed6292

45 files changed

Lines changed: 414 additions & 130 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

bazel/repository_locations.bzl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ REPOSITORY_LOCATIONS = dict(
6060
urls = ["https://github.com/google/protobuf/archive/v3.5.0.tar.gz"],
6161
),
6262
envoy_api = dict(
63-
commit = "5055a888929d6aca545bff0159ab937ee40532f3",
63+
commit = "ddf6b6206148fd2342172642cf56451316bc870d",
6464
remote = "https://github.com/envoyproxy/data-plane-api",
6565
),
6666
grpc_httpjson_transcoding = dict(

source/common/protobuf/utility.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@ MissingFieldException::MissingFieldException(const std::string& field_name,
1414
: EnvoyException(
1515
fmt::format("Field '{}' is missing in: {}", field_name, message.DebugString())) {}
1616

17+
ProtoValidationException::ProtoValidationException(const std::string& validation_error,
18+
const Protobuf::Message& message)
19+
: EnvoyException(fmt::format("Proto constraint validation failed ({}): {}", validation_error,
20+
message.DebugString())) {
21+
ENVOY_LOG_MISC(debug, "Proto validation error; throwing {}", what());
22+
}
23+
1724
void MessageUtil::loadFromJson(const std::string& json, Protobuf::Message& message) {
1825
const auto status = Protobuf::util::JsonStringToMessage(json, &message);
1926
if (!status.ok()) {

source/common/protobuf/utility.h

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include "common/json/json_loader.h"
1111
#include "common/protobuf/protobuf.h"
1212

13+
#include "fmt/format.h"
14+
1315
// Obtain the value of a wrapped field (e.g. google.protobuf.UInt32Value) if set. Otherwise, return
1416
// the default value.
1517
#define PROTOBUF_GET_WRAPPED_OR_DEFAULT(message, field_name, default_value) \
@@ -88,6 +90,11 @@ class RepeatedPtrUtil {
8890
}
8991
};
9092

93+
class ProtoValidationException : public EnvoyException {
94+
public:
95+
ProtoValidationException(const std::string& validation_error, const Protobuf::Message& message);
96+
};
97+
9198
class MessageUtil {
9299
public:
93100
static std::size_t hash(const Protobuf::Message& message) {
@@ -109,6 +116,37 @@ class MessageUtil {
109116
static void loadFromYaml(const std::string& yaml, Protobuf::Message& message);
110117
static void loadFromFile(const std::string& path, Protobuf::Message& message);
111118

119+
/**
120+
* Validate protoc-gen-validate constraints on a given protobuf.
121+
* @param message message to validate.
122+
* @throw ProtoValidationException if the message does not satisfy its type constraints.
123+
*/
124+
template <class MessageType> static void validate(const MessageType& message) {
125+
std::string err;
126+
if (!Validate(message, &err)) {
127+
throw ProtoValidationException(err, message);
128+
}
129+
}
130+
131+
template <class MessageType>
132+
static void loadFromFileAndValidate(const std::string& path, MessageType& message) {
133+
loadFromFile(path, message);
134+
validate(message);
135+
}
136+
137+
/**
138+
* Downcast and validate protoc-gen-validate constraints on a given protobuf.
139+
* @param message const Protobuf::Message& to downcast and validate.
140+
* @return const MessageType& the concrete message type downcasted to on success.
141+
* @throw ProtoValidationException if the message does not satisfy its type constraints.
142+
*/
143+
template <class MessageType>
144+
static const MessageType& downcastAndValidate(const Protobuf::Message& config) {
145+
const auto& typed_config = dynamic_cast<MessageType>(config);
146+
validate(typed_config);
147+
return typed_config;
148+
}
149+
112150
/**
113151
* Convert from google.protobuf.Any to a typed message.
114152
* @param message source google.protobuf.Any message.

source/common/router/rds_impl.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
#include "common/config/rds_json.h"
1010
#include "common/config/subscription_factory.h"
1111
#include "common/config/utility.h"
12+
#include "common/protobuf/utility.h"
1213
#include "common/router/config_impl.h"
1314
#include "common/router/rds_subscription.h"
1415

16+
#include "api/rds.pb.validate.h"
1517
#include "fmt/format.h"
1618

1719
namespace Envoy {
@@ -104,6 +106,7 @@ void RdsRouteConfigProviderImpl::onConfigUpdate(const ResourceVector& resources)
104106
throw EnvoyException(fmt::format("Unexpected RDS resource length: {}", resources.size()));
105107
}
106108
const auto& route_config = resources[0];
109+
MessageUtil::validate(route_config);
107110
// TODO(PiotrSikora): Remove this hack once fixed internally.
108111
if (!(route_config.name() == route_config_name_)) {
109112
throw EnvoyException(fmt::format("Unexpected RDS configuration (expecting {}): {}",

source/common/upstream/BUILD

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ envoy_cc_library(
2323
"//source/common/config:resources_lib",
2424
"//source/common/config:subscription_factory_lib",
2525
"//source/common/config:utility_lib",
26+
"//source/common/protobuf:utility_lib",
2627
],
2728
)
2829

@@ -239,6 +240,7 @@ envoy_cc_library(
239240
"//source/common/network:address_lib",
240241
"//source/common/network:resolver_lib",
241242
"//source/common/network:utility_lib",
243+
"//source/common/protobuf:utility_lib",
242244
],
243245
)
244246

source/common/upstream/cds_api_impl.cc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,11 @@
66
#include "common/config/resources.h"
77
#include "common/config/subscription_factory.h"
88
#include "common/config/utility.h"
9+
#include "common/protobuf/utility.h"
910
#include "common/upstream/cds_subscription.h"
1011

12+
#include "api/cds.pb.validate.h"
13+
1114
namespace Envoy {
1215
namespace Upstream {
1316

@@ -41,6 +44,9 @@ CdsApiImpl::CdsApiImpl(const envoy::api::v2::ConfigSource& cds_config,
4144
void CdsApiImpl::onConfigUpdate(const ResourceVector& resources) {
4245
cm_.adsMux().pause(Config::TypeUrl::get().ClusterLoadAssignment);
4346
Cleanup eds_resume([this] { cm_.adsMux().resume(Config::TypeUrl::get().ClusterLoadAssignment); });
47+
for (const auto& cluster : resources) {
48+
MessageUtil::validate(cluster);
49+
}
4450
// We need to keep track of which clusters we might need to remove.
4551
ClusterManager::ClusterInfoMap clusters_to_remove = cm_.clusters();
4652
for (auto& cluster : resources) {

source/common/upstream/cds_api_impl.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,17 @@ class CdsApiImpl : public CdsApi,
3434
}
3535
const std::string versionInfo() const override { return subscription_->versionInfo(); }
3636

37+
// Config::SubscriptionCallbacks
38+
void onConfigUpdate(const ResourceVector& resources) override;
39+
void onConfigUpdateFailed(const EnvoyException* e) override;
40+
3741
private:
3842
CdsApiImpl(const envoy::api::v2::ConfigSource& cds_config,
3943
const Optional<envoy::api::v2::ConfigSource>& eds_config, ClusterManager& cm,
4044
Event::Dispatcher& dispatcher, Runtime::RandomGenerator& random,
4145
const LocalInfo::LocalInfo& local_info, Stats::Scope& scope);
4246
void runInitializeCallbackIfAny();
4347

44-
// Config::SubscriptionCallbacks
45-
void onConfigUpdate(const ResourceVector& resources) override;
46-
void onConfigUpdateFailed(const EnvoyException* e) override;
47-
4848
ClusterManager& cm_;
4949
std::unique_ptr<Config::Subscription<envoy::api::v2::Cluster>> subscription_;
5050
std::function<void()> initialize_callback_;

source/common/upstream/eds.cc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
#include "common/network/address_impl.h"
1010
#include "common/network/resolver_impl.h"
1111
#include "common/network/utility.h"
12+
#include "common/protobuf/utility.h"
1213
#include "common/upstream/sds_subscription.h"
1314

15+
#include "api/eds.pb.validate.h"
1416
#include "fmt/format.h"
1517

1618
namespace Envoy {
@@ -54,6 +56,7 @@ void EdsClusterImpl::onConfigUpdate(const ResourceVector& resources) {
5456
throw EnvoyException(fmt::format("Unexpected EDS resource length: {}", resources.size()));
5557
}
5658
const auto& cluster_load_assignment = resources[0];
59+
MessageUtil::validate(cluster_load_assignment);
5760
// TODO(PiotrSikora): Remove this hack once fixed internally.
5861
if (!(cluster_load_assignment.cluster_name() == cluster_name_)) {
5962
throw EnvoyException(fmt::format("Unexpected EDS cluster (expecting {}): {}", cluster_name_,

source/server/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ envoy_cc_library(
167167
"//source/common/config:resources_lib",
168168
"//source/common/config:subscription_factory_lib",
169169
"//source/common/config:utility_lib",
170+
"//source/common/protobuf:utility_lib",
170171
],
171172
)
172173

source/server/config/http/buffer.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
#include "common/http/filter/buffer_filter.h"
1111
#include "common/protobuf/utility.h"
1212

13+
#include "api/filter/http/buffer.pb.validate.h"
14+
1315
namespace Envoy {
1416
namespace Server {
1517
namespace Configuration {
@@ -42,8 +44,9 @@ HttpFilterFactoryCb
4244
BufferFilterConfig::createFilterFactoryFromProto(const Protobuf::Message& proto_config,
4345
const std::string& stats_prefix,
4446
FactoryContext& context) {
45-
return createFilter(dynamic_cast<const envoy::api::v2::filter::http::Buffer&>(proto_config),
46-
stats_prefix, context);
47+
return createFilter(
48+
MessageUtil::downcastAndValidate<const envoy::api::v2::filter::http::Buffer&>(proto_config),
49+
stats_prefix, context);
4750
}
4851

4952
/**

0 commit comments

Comments
 (0)