/proc/self/cwd/source/common/protobuf/utility.cc
Line | Count | Source (jump to first uncovered line) |
1 | | #include "source/common/protobuf/utility.h" |
2 | | |
3 | | #include <limits> |
4 | | #include <numeric> |
5 | | |
6 | | #include "envoy/annotations/deprecation.pb.h" |
7 | | #include "envoy/protobuf/message_validator.h" |
8 | | #include "envoy/type/v3/percent.pb.h" |
9 | | |
10 | | #include "source/common/common/assert.h" |
11 | | #include "source/common/common/documentation_url.h" |
12 | | #include "source/common/common/fmt.h" |
13 | | #include "source/common/protobuf/message_validator_impl.h" |
14 | | #include "source/common/protobuf/protobuf.h" |
15 | | #include "source/common/protobuf/visitor.h" |
16 | | #include "source/common/runtime/runtime_features.h" |
17 | | |
18 | | #include "absl/strings/match.h" |
19 | | #include "udpa/annotations/sensitive.pb.h" |
20 | | #include "udpa/annotations/status.pb.h" |
21 | | #include "validate/validate.h" |
22 | | #include "xds/annotations/v3/status.pb.h" |
23 | | |
24 | | using namespace std::chrono_literals; |
25 | | |
26 | | namespace Envoy { |
27 | | namespace { |
28 | | |
29 | 44.5M | absl::string_view filenameFromPath(absl::string_view full_path) { |
30 | 44.5M | size_t index = full_path.rfind('/'); |
31 | 44.5M | if (index == std::string::npos || index == full_path.size()) { |
32 | 0 | return full_path; |
33 | 0 | } |
34 | 44.5M | return full_path.substr(index + 1, full_path.size()); |
35 | 44.5M | } |
36 | | |
37 | | // Logs a warning for use of a deprecated field or runtime-overridden use of an |
38 | | // otherwise fatal field. Throws a warning on use of a fatal by default field. |
39 | | void deprecatedFieldHelper(Runtime::Loader* runtime, bool proto_annotated_as_deprecated, |
40 | | bool proto_annotated_as_disallowed, const std::string& feature_name, |
41 | | std::string error, const Protobuf::Message& message, |
42 | 150k | ProtobufMessage::ValidationVisitor& validation_visitor) { |
43 | | // This option is for Envoy builds with --define deprecated_features=disabled |
44 | | // The build options CI then verifies that as Envoy developers deprecate fields, |
45 | | // that they update canonical configs and unit tests to not use those deprecated |
46 | | // fields, by making their use fatal in the build options CI. |
47 | | #ifdef ENVOY_DISABLE_DEPRECATED_FEATURES |
48 | | bool warn_only = false; |
49 | | #else |
50 | 150k | bool warn_only = true; |
51 | 150k | #endif |
52 | 150k | if (runtime && |
53 | 150k | runtime->snapshot().getBoolean("envoy.features.fail_on_any_deprecated_feature", false)) { |
54 | 0 | warn_only = false; |
55 | 0 | } |
56 | 150k | bool warn_default = warn_only; |
57 | | // Allow runtime to be null both to not crash if this is called before server initialization, |
58 | | // and so proto validation works in context where runtime singleton is not set up (e.g. |
59 | | // standalone config validation utilities) |
60 | 150k | if (runtime && proto_annotated_as_deprecated) { |
61 | | // This is set here, rather than above, so that in the absence of a |
62 | | // registry (i.e. test) the default for if a feature is allowed or not is |
63 | | // based on ENVOY_DISABLE_DEPRECATED_FEATURES. |
64 | 0 | warn_only &= !proto_annotated_as_disallowed; |
65 | 0 | warn_default = warn_only; |
66 | 0 | warn_only = runtime->snapshot().deprecatedFeatureEnabled(feature_name, warn_only); |
67 | 0 | } |
68 | | // Note this only checks if the runtime override has an actual effect. It |
69 | | // does not change the logged warning if someone "allows" a deprecated but not |
70 | | // yet fatal field. |
71 | 150k | const bool runtime_overridden = (warn_default == false && warn_only == true); |
72 | | |
73 | 150k | std::string with_overridden = fmt::format( |
74 | 150k | fmt::runtime(error), |
75 | 150k | (runtime_overridden ? "runtime overrides to continue using now fatal-by-default " : "")); |
76 | | |
77 | 150k | validation_visitor.onDeprecatedField("type " + message.GetTypeName() + " " + with_overridden, |
78 | 150k | warn_only); |
79 | 150k | } |
80 | | |
81 | | } // namespace |
82 | | |
83 | | namespace ProtobufPercentHelper { |
84 | | |
85 | 10.5k | uint64_t checkAndReturnDefault(uint64_t default_value, uint64_t max_value) { |
86 | 10.5k | ASSERT(default_value <= max_value); |
87 | 10.5k | return default_value; |
88 | 10.5k | } |
89 | | |
90 | 422 | uint64_t convertPercent(double percent, uint64_t max_value) { |
91 | | // Checked by schema. |
92 | 422 | ASSERT(percent >= 0.0 && percent <= 100.0); |
93 | 422 | return max_value * (percent / 100.0); |
94 | 422 | } |
95 | | |
96 | 2.71k | bool evaluateFractionalPercent(envoy::type::v3::FractionalPercent percent, uint64_t random_value) { |
97 | 2.71k | return random_value % fractionalPercentDenominatorToInt(percent.denominator()) < |
98 | 2.71k | percent.numerator(); |
99 | 2.71k | } |
100 | | |
101 | | uint64_t fractionalPercentDenominatorToInt( |
102 | 6.71k | const envoy::type::v3::FractionalPercent::DenominatorType& denominator) { |
103 | 6.71k | switch (denominator) { |
104 | 0 | PANIC_ON_PROTO_ENUM_SENTINEL_VALUES; |
105 | 6.37k | case envoy::type::v3::FractionalPercent::HUNDRED: |
106 | 6.37k | return 100; |
107 | 0 | case envoy::type::v3::FractionalPercent::TEN_THOUSAND: |
108 | 0 | return 10000; |
109 | 341 | case envoy::type::v3::FractionalPercent::MILLION: |
110 | 341 | return 1000000; |
111 | 6.71k | } |
112 | 0 | PANIC_DUE_TO_CORRUPT_ENUM |
113 | 0 | } |
114 | | |
115 | | } // namespace ProtobufPercentHelper |
116 | | |
117 | | MissingFieldException::MissingFieldException(const std::string& message) |
118 | 187 | : EnvoyException(message) {} |
119 | | |
120 | | ProtoValidationException::ProtoValidationException(const std::string& message) |
121 | 3.81k | : EnvoyException(message) { |
122 | 3.81k | ENVOY_LOG_MISC(debug, "Proto validation error; throwing {}", what()); |
123 | 3.81k | } |
124 | | |
125 | | void ProtoExceptionUtil::throwMissingFieldException(const std::string& field_name, |
126 | 187 | const Protobuf::Message& message) { |
127 | 187 | std::string error = |
128 | 187 | fmt::format("Field '{}' is missing in: {}", field_name, message.DebugString()); |
129 | 187 | throwExceptionOrPanic(MissingFieldException, error); |
130 | 187 | } |
131 | | |
132 | | void ProtoExceptionUtil::throwProtoValidationException(const std::string& validation_error, |
133 | 3.80k | const Protobuf::Message& message) { |
134 | 3.80k | std::string error = fmt::format("Proto constraint validation failed ({}): {}", validation_error, |
135 | 3.80k | message.DebugString()); |
136 | 3.80k | throwExceptionOrPanic(ProtoValidationException, error); |
137 | 3.80k | } |
138 | | |
139 | 205k | size_t MessageUtil::hash(const Protobuf::Message& message) { |
140 | 205k | std::string text_format; |
141 | | |
142 | 205k | #if defined(ENVOY_ENABLE_FULL_PROTOS) |
143 | 205k | { |
144 | 205k | Protobuf::TextFormat::Printer printer; |
145 | 205k | printer.SetExpandAny(true); |
146 | 205k | printer.SetUseFieldNumber(true); |
147 | 205k | printer.SetSingleLineMode(true); |
148 | 205k | printer.SetHideUnknownFields(true); |
149 | 205k | printer.PrintToString(message, &text_format); |
150 | 205k | } |
151 | | #else |
152 | | absl::StrAppend(&text_format, message.SerializeAsString()); |
153 | | #endif |
154 | | |
155 | 205k | return HashUtil::xxHash64(text_format); |
156 | 205k | } |
157 | | |
158 | | #if !defined(ENVOY_ENABLE_FULL_PROTOS) |
159 | | // NOLINTNEXTLINE(readability-identifier-naming) |
160 | | bool MessageLiteDifferencer::Equals(const Protobuf::Message& message1, |
161 | | const Protobuf::Message& message2) { |
162 | | return MessageUtil::hash(message1) == MessageUtil::hash(message2); |
163 | | } |
164 | | |
165 | | // NOLINTNEXTLINE(readability-identifier-naming) |
166 | | bool MessageLiteDifferencer::Equivalent(const Protobuf::Message& message1, |
167 | | const Protobuf::Message& message2) { |
168 | | return Equals(message1, message2); |
169 | | } |
170 | | #endif |
171 | | |
172 | | namespace { |
173 | | |
174 | | void checkForDeprecatedNonRepeatedEnumValue( |
175 | | const Protobuf::Message& message, absl::string_view filename, |
176 | | const Protobuf::FieldDescriptor* field, const Protobuf::Reflection* reflection, |
177 | 44.5M | Runtime::Loader* runtime, ProtobufMessage::ValidationVisitor& validation_visitor) { |
178 | | // Repeated fields will be handled by recursion in checkForUnexpectedFields. |
179 | 44.5M | if (field->is_repeated() || field->cpp_type() != Protobuf::FieldDescriptor::CPPTYPE_ENUM) { |
180 | 43.0M | return; |
181 | 43.0M | } |
182 | | |
183 | 1.51M | Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); |
184 | 1.51M | bool default_value = !reflection->HasField(*reflectable_message, field); |
185 | | |
186 | 1.51M | const Protobuf::EnumValueDescriptor* enum_value_descriptor = |
187 | 1.51M | reflection->GetEnum(*reflectable_message, field); |
188 | 1.51M | if (!enum_value_descriptor->options().deprecated()) { |
189 | 1.50M | return; |
190 | 1.50M | } |
191 | | |
192 | 4.96k | const std::string error = |
193 | 4.96k | absl::StrCat("Using {}", (default_value ? "the default now-" : ""), "deprecated value ", |
194 | 4.96k | enum_value_descriptor->name(), " for enum '", field->full_name(), "' from file ", |
195 | 4.96k | filename, ". This enum value will be removed from Envoy soon", |
196 | 4.96k | (default_value ? " so a non-default value must now be explicitly set" : ""), |
197 | 4.96k | ". Please see " ENVOY_DOC_URL_VERSION_HISTORY " for details."); |
198 | 4.96k | deprecatedFieldHelper( |
199 | 4.96k | runtime, true /*deprecated*/, |
200 | 4.96k | enum_value_descriptor->options().GetExtension(envoy::annotations::disallowed_by_default_enum), |
201 | 4.96k | absl::StrCat("envoy.deprecated_features:", enum_value_descriptor->full_name()), error, |
202 | 4.96k | message, validation_visitor); |
203 | 4.96k | } |
204 | | |
205 | | constexpr absl::string_view WipWarning = |
206 | | "API features marked as work-in-progress are not considered stable, are not covered by the " |
207 | | "threat model, are not supported by the security team, and are subject to breaking changes. Do " |
208 | | "not use this feature without understanding each of the previous points."; |
209 | | |
210 | | class UnexpectedFieldProtoVisitor : public ProtobufMessage::ConstProtoVisitor { |
211 | | public: |
212 | | UnexpectedFieldProtoVisitor(ProtobufMessage::ValidationVisitor& validation_visitor, |
213 | | Runtime::Loader* runtime) |
214 | 274k | : validation_visitor_(validation_visitor), runtime_(runtime) {} |
215 | | |
216 | 44.5M | void onField(const Protobuf::Message& message, const Protobuf::FieldDescriptor& field) override { |
217 | 44.5M | Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); |
218 | 44.5M | const Protobuf::Reflection* reflection = reflectable_message->GetReflection(); |
219 | 44.5M | absl::string_view filename = filenameFromPath(field.file()->name()); |
220 | | |
221 | | // Before we check to see if the field is in use, see if there's a |
222 | | // deprecated default enum value. |
223 | 44.5M | checkForDeprecatedNonRepeatedEnumValue(message, filename, &field, reflection, runtime_, |
224 | 44.5M | validation_visitor_); |
225 | | |
226 | | // If this field is not in use, continue. |
227 | 44.5M | if ((field.is_repeated() && reflection->FieldSize(*reflectable_message, &field) == 0) || |
228 | 44.5M | (!field.is_repeated() && !reflection->HasField(*reflectable_message, &field))) { |
229 | 31.5M | return; |
230 | 31.5M | } |
231 | | |
232 | 13.0M | const auto& field_status = field.options().GetExtension(xds::annotations::v3::field_status); |
233 | 13.0M | if (field_status.work_in_progress()) { |
234 | 10.2k | validation_visitor_.onWorkInProgress(fmt::format( |
235 | 10.2k | "field '{}' is marked as work-in-progress. {}", field.full_name(), WipWarning)); |
236 | 10.2k | } |
237 | | |
238 | | // If this field is deprecated, warn or throw an error. |
239 | 13.0M | if (field.options().deprecated()) { |
240 | 145k | const std::string warning = |
241 | 145k | absl::StrCat("Using {}deprecated option '", field.full_name(), "' from file ", filename, |
242 | 145k | ". This configuration will be removed from " |
243 | 145k | "Envoy soon. Please see " ENVOY_DOC_URL_VERSION_HISTORY " for details."); |
244 | | |
245 | 145k | deprecatedFieldHelper(runtime_, true /*deprecated*/, |
246 | 145k | field.options().GetExtension(envoy::annotations::disallowed_by_default), |
247 | 145k | absl::StrCat("envoy.deprecated_features:", field.full_name()), warning, |
248 | 145k | message, validation_visitor_); |
249 | 145k | } |
250 | 13.0M | } |
251 | | |
252 | | void onMessage(const Protobuf::Message& message, |
253 | 10.1M | absl::Span<const Protobuf::Message* const> parents, bool) override { |
254 | 10.1M | Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); |
255 | 10.1M | if (reflectable_message->GetDescriptor() |
256 | 10.1M | ->options() |
257 | 10.1M | .GetExtension(xds::annotations::v3::message_status) |
258 | 10.1M | .work_in_progress()) { |
259 | 32.0k | validation_visitor_.onWorkInProgress(fmt::format( |
260 | 32.0k | "message '{}' is marked as work-in-progress. {}", message.GetTypeName(), WipWarning)); |
261 | 32.0k | } |
262 | | |
263 | 10.1M | const auto& udpa_file_options = |
264 | 10.1M | reflectable_message->GetDescriptor()->file()->options().GetExtension( |
265 | 10.1M | udpa::annotations::file_status); |
266 | 10.1M | const auto& xds_file_options = |
267 | 10.1M | reflectable_message->GetDescriptor()->file()->options().GetExtension( |
268 | 10.1M | xds::annotations::v3::file_status); |
269 | 10.1M | if (udpa_file_options.work_in_progress() || xds_file_options.work_in_progress()) { |
270 | 1.70k | validation_visitor_.onWorkInProgress(fmt::format( |
271 | 1.70k | "message '{}' is contained in proto file '{}' marked as work-in-progress. {}", |
272 | 1.70k | message.GetTypeName(), reflectable_message->GetDescriptor()->file()->name(), WipWarning)); |
273 | 1.70k | } |
274 | | |
275 | | // Reject unknown fields. |
276 | 10.1M | const auto& unknown_fields = |
277 | 10.1M | reflectable_message->GetReflection()->GetUnknownFields(*reflectable_message); |
278 | 10.1M | if (!unknown_fields.empty()) { |
279 | 22 | std::string error_msg; |
280 | 117 | for (int n = 0; n < unknown_fields.field_count(); ++n) { |
281 | 95 | absl::StrAppend(&error_msg, n > 0 ? ", " : "", unknown_fields.field(n).number()); |
282 | 95 | } |
283 | 22 | if (!error_msg.empty()) { |
284 | 22 | validation_visitor_.onUnknownField( |
285 | 22 | fmt::format("type {}({}) with unknown field set {{{}}}", message.GetTypeName(), |
286 | 22 | !parents.empty() |
287 | 22 | ? absl::StrJoin(parents, "::", |
288 | 18 | [](std::string* out, const Protobuf::Message* const m) { |
289 | 18 | absl::StrAppend(out, m->GetTypeName()); |
290 | 18 | }) |
291 | 22 | : "root", |
292 | 22 | error_msg)); |
293 | 22 | } |
294 | 22 | } |
295 | 10.1M | } |
296 | | |
297 | | private: |
298 | | ProtobufMessage::ValidationVisitor& validation_visitor_; |
299 | | Runtime::Loader* runtime_; |
300 | | }; |
301 | | |
302 | | } // namespace |
303 | | |
304 | | void MessageUtil::checkForUnexpectedFields(const Protobuf::Message& message, |
305 | | ProtobufMessage::ValidationVisitor& validation_visitor, |
306 | 274k | bool recurse_into_any) { |
307 | 274k | Runtime::Loader* runtime = validation_visitor.runtime().has_value() |
308 | 274k | ? &validation_visitor.runtime().value().get() |
309 | 274k | : nullptr; |
310 | 274k | UnexpectedFieldProtoVisitor unexpected_field_visitor(validation_visitor, runtime); |
311 | 274k | ProtobufMessage::traverseMessage(unexpected_field_visitor, message, recurse_into_any); |
312 | 274k | } |
313 | | |
314 | | namespace { |
315 | | |
316 | | class PgvCheckVisitor : public ProtobufMessage::ConstProtoVisitor { |
317 | | public: |
318 | | void onMessage(const Protobuf::Message& message, absl::Span<const Protobuf::Message* const>, |
319 | 0 | bool was_any_or_top_level) override { |
320 | 0 | Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(message); |
321 | 0 | std::string err; |
322 | | // PGV verification is itself recursive up to the point at which it hits an Any message. As |
323 | | // such, to avoid N^2 checking of the tree, we only perform an additional check at the point |
324 | | // at which PGV would have stopped because it does not itself check within Any messages. |
325 | 0 | if (was_any_or_top_level && |
326 | 0 | !pgv::BaseValidator::AbstractCheckMessage(*reflectable_message, &err)) { |
327 | 0 | ProtoExceptionUtil::throwProtoValidationException(err, *reflectable_message); |
328 | 0 | } |
329 | 0 | } |
330 | | |
331 | 0 | void onField(const Protobuf::Message&, const Protobuf::FieldDescriptor&) override {} |
332 | | }; |
333 | | |
334 | | } // namespace |
335 | | |
336 | 0 | void MessageUtil::recursivePgvCheck(const Protobuf::Message& message) { |
337 | 0 | PgvCheckVisitor visitor; |
338 | 0 | ProtobufMessage::traverseMessage(visitor, message, true); |
339 | 0 | } |
340 | | |
341 | 2.65k | void MessageUtil::packFrom(ProtobufWkt::Any& any_message, const Protobuf::Message& message) { |
342 | 2.65k | #if defined(ENVOY_ENABLE_FULL_PROTOS) |
343 | 2.65k | any_message.PackFrom(message); |
344 | | #else |
345 | | any_message.set_type_url(message.GetTypeName()); |
346 | | any_message.set_value(message.SerializeAsString()); |
347 | | #endif |
348 | 2.65k | } |
349 | | |
350 | 200k | void MessageUtil::unpackTo(const ProtobufWkt::Any& any_message, Protobuf::Message& message) { |
351 | 200k | #if defined(ENVOY_ENABLE_FULL_PROTOS) |
352 | 200k | if (!any_message.UnpackTo(&message)) { |
353 | 61 | throwEnvoyExceptionOrPanic(fmt::format("Unable to unpack as {}: {}", |
354 | 61 | message.GetDescriptor()->full_name(), |
355 | 61 | any_message.DebugString())); |
356 | | #else |
357 | | if (!message.ParseFromString(any_message.value())) { |
358 | | throwEnvoyExceptionOrPanic( |
359 | | fmt::format("Unable to unpack as {}: {}", message.GetTypeName(), any_message.type_url())); |
360 | | #endif |
361 | 61 | } |
362 | 200k | } |
363 | | |
364 | | absl::Status MessageUtil::unpackToNoThrow(const ProtobufWkt::Any& any_message, |
365 | 0 | Protobuf::Message& message) { |
366 | 0 | #if defined(ENVOY_ENABLE_FULL_PROTOS) |
367 | 0 | if (!any_message.UnpackTo(&message)) { |
368 | 0 | return absl::InternalError(absl::StrCat("Unable to unpack as ", |
369 | 0 | message.GetDescriptor()->full_name(), ": ", |
370 | 0 | any_message.DebugString())); |
371 | | #else |
372 | | if (!message.ParseFromString(any_message.value())) { |
373 | | return absl::InternalError( |
374 | | absl::StrCat("Unable to unpack as ", message.GetTypeName(), ": ", any_message.type_url())); |
375 | | #endif |
376 | 0 | } |
377 | | // Ok Status is returned if `UnpackTo` succeeded. |
378 | 0 | return absl::OkStatus(); |
379 | 0 | } |
380 | | |
381 | 0 | ProtobufWkt::Struct MessageUtil::keyValueStruct(const std::string& key, const std::string& value) { |
382 | 0 | ProtobufWkt::Struct struct_obj; |
383 | 0 | ProtobufWkt::Value val; |
384 | 0 | val.set_string_value(value); |
385 | 0 | (*struct_obj.mutable_fields())[key] = val; |
386 | 0 | return struct_obj; |
387 | 0 | } |
388 | | |
389 | 13 | ProtobufWkt::Struct MessageUtil::keyValueStruct(const std::map<std::string, std::string>& fields) { |
390 | 13 | ProtobufWkt::Struct struct_obj; |
391 | 13 | ProtobufWkt::Value val; |
392 | 65 | for (const auto& pair : fields) { |
393 | 65 | val.set_string_value(pair.second); |
394 | 65 | (*struct_obj.mutable_fields())[pair.first] = val; |
395 | 65 | } |
396 | 13 | return struct_obj; |
397 | 13 | } |
398 | | |
399 | 149 | std::string MessageUtil::codeEnumToString(absl::StatusCode code) { |
400 | 149 | std::string result = absl::StatusCodeToString(code); |
401 | | // This preserves the behavior of the `ProtobufUtil::Status(code, "").ToString();` |
402 | 149 | return !result.empty() ? result : "UNKNOWN: "; |
403 | 149 | } |
404 | | |
405 | | namespace { |
406 | | |
407 | | // Forward declaration for mutually-recursive helper functions. |
408 | | void redact(Protobuf::Message* message, bool ancestor_is_sensitive); |
409 | | |
410 | | using Transform = std::function<void(Protobuf::Message*, const Protobuf::Reflection*, |
411 | | const Protobuf::FieldDescriptor*)>; |
412 | | |
413 | | // To redact opaque types, namely `Any` and `TypedStruct`, we have to reify them to the concrete |
414 | | // message types specified by their `type_url` before we can redact their contents. This is mostly |
415 | | // identical between `Any` and `TypedStruct`, the only difference being how they are packed and |
416 | | // unpacked. Note that we have to use reflection on the opaque type here, rather than downcasting |
417 | | // to `Any` or `TypedStruct`, because any message we might be handling could have originated from |
418 | | // a `DynamicMessageFactory`. |
419 | | bool redactOpaque(Protobuf::Message* message, bool ancestor_is_sensitive, |
420 | 1.69M | absl::string_view opaque_type_name, Transform unpack, Transform repack) { |
421 | | // Ensure this message has the opaque type we're expecting. |
422 | 1.69M | Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message); |
423 | 1.69M | const auto* opaque_descriptor = reflectable_message->GetDescriptor(); |
424 | 1.69M | if (opaque_descriptor->full_name() != opaque_type_name) { |
425 | 1.64M | return false; |
426 | 1.64M | } |
427 | | |
428 | | // Find descriptors for the `type_url` and `value` fields. The `type_url` field must not be |
429 | | // empty, but `value` may be (in which case our work is done). |
430 | 42.6k | const auto* reflection = reflectable_message->GetReflection(); |
431 | 42.6k | const auto* type_url_field_descriptor = opaque_descriptor->FindFieldByName("type_url"); |
432 | 42.6k | const auto* value_field_descriptor = opaque_descriptor->FindFieldByName("value"); |
433 | 42.6k | ASSERT(type_url_field_descriptor != nullptr && value_field_descriptor != nullptr); |
434 | 42.6k | if (!reflection->HasField(*reflectable_message, type_url_field_descriptor) && |
435 | 42.6k | !reflection->HasField(*reflectable_message, value_field_descriptor)) { |
436 | 0 | return true; |
437 | 0 | } |
438 | 42.6k | if (!reflection->HasField(*reflectable_message, type_url_field_descriptor) || |
439 | 42.6k | !reflection->HasField(*reflectable_message, value_field_descriptor)) { |
440 | 5.29k | return false; |
441 | 5.29k | } |
442 | | |
443 | | // Try to find a descriptor for `type_url` in the pool and instantiate a new message of the |
444 | | // correct concrete type. |
445 | 37.3k | const std::string type_url( |
446 | 37.3k | reflection->GetString(*reflectable_message, type_url_field_descriptor)); |
447 | 37.3k | const std::string concrete_type_name(TypeUtil::typeUrlToDescriptorFullName(type_url)); |
448 | 37.3k | const auto* concrete_descriptor = |
449 | 37.3k | Protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName(concrete_type_name); |
450 | 37.3k | if (concrete_descriptor == nullptr) { |
451 | | // If the type URL doesn't correspond to a known proto, don't try to reify it, just treat it |
452 | | // like any other message. See the documented limitation on `MessageUtil::redact()` for more |
453 | | // context. |
454 | 0 | ENVOY_LOG_MISC(warn, "Could not reify {} with unknown type URL {}", opaque_type_name, type_url); |
455 | 0 | return false; |
456 | 0 | } |
457 | 37.3k | Protobuf::DynamicMessageFactory message_factory; |
458 | 37.3k | std::unique_ptr<Protobuf::Message> typed_message( |
459 | 37.3k | message_factory.GetPrototype(concrete_descriptor)->New()); |
460 | | |
461 | | // Finally we can unpack, redact, and repack the opaque message using the provided callbacks. |
462 | | |
463 | | // Note: the content of opaque types may contain illegal content that mismatches the type_url |
464 | | // which may cause unpacking to fail. We catch the exception here to avoid crashing Envoy. |
465 | 37.3k | TRY_ASSERT_MAIN_THREAD { unpack(typed_message.get(), reflection, value_field_descriptor); } |
466 | 37.3k | END_TRY CATCH(const EnvoyException& e, { |
467 | 37.3k | ENVOY_LOG_MISC(warn, "Could not unpack {} with type URL {}: {}", opaque_type_name, type_url, |
468 | 37.3k | e.what()); |
469 | 37.3k | return false; |
470 | 37.3k | }); |
471 | 37.3k | redact(typed_message.get(), ancestor_is_sensitive); |
472 | 37.3k | repack(typed_message.get(), reflection, value_field_descriptor); |
473 | 37.3k | return true; |
474 | 37.3k | } |
475 | | |
476 | 588k | bool redactAny(Protobuf::Message* message, bool ancestor_is_sensitive) { |
477 | 588k | return redactOpaque( |
478 | 588k | message, ancestor_is_sensitive, "google.protobuf.Any", |
479 | 588k | [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection, |
480 | 588k | const Protobuf::FieldDescriptor* field_descriptor) { |
481 | 37.3k | Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message); |
482 | | // To unpack an `Any`, parse the serialized proto. |
483 | 37.3k | typed_message->ParseFromString( |
484 | 37.3k | reflection->GetString(*reflectable_message, field_descriptor)); |
485 | 37.3k | }, |
486 | 588k | [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection, |
487 | 588k | const Protobuf::FieldDescriptor* field_descriptor) { |
488 | 37.3k | Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message); |
489 | | // To repack an `Any`, reserialize its proto. |
490 | 37.3k | reflection->SetString(&(*reflectable_message), field_descriptor, |
491 | 37.3k | typed_message->SerializeAsString()); |
492 | 37.3k | }); |
493 | 588k | } |
494 | | |
495 | | // To redact a `TypedStruct`, we have to reify it based on its `type_url` to redact it. |
496 | | bool redactTypedStruct(Protobuf::Message* message, const char* typed_struct_type, |
497 | 1.10M | bool ancestor_is_sensitive) { |
498 | 1.10M | return redactOpaque( |
499 | 1.10M | message, ancestor_is_sensitive, typed_struct_type, |
500 | 1.10M | [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection, |
501 | 1.10M | const Protobuf::FieldDescriptor* field_descriptor) { |
502 | 0 | #ifdef ENVOY_ENABLE_YAML |
503 | | // To unpack a `TypedStruct`, convert the struct from JSON. |
504 | 0 | MessageUtil::jsonConvert(reflection->GetMessage(*message, field_descriptor), |
505 | 0 | *typed_message); |
506 | | #else |
507 | | UNREFERENCED_PARAMETER(message); |
508 | | UNREFERENCED_PARAMETER(typed_message); |
509 | | UNREFERENCED_PARAMETER(reflection); |
510 | | UNREFERENCED_PARAMETER(field_descriptor); |
511 | | IS_ENVOY_BUG("redaction requested with JSON/YAML support removed"); |
512 | | #endif |
513 | 0 | }, |
514 | 1.10M | [message](Protobuf::Message* typed_message, const Protobuf::Reflection* reflection, |
515 | 1.10M | const Protobuf::FieldDescriptor* field_descriptor) { |
516 | | // To repack a `TypedStruct`, convert the message back to JSON. |
517 | 0 | #ifdef ENVOY_ENABLE_YAML |
518 | 0 | MessageUtil::jsonConvert(*typed_message, |
519 | 0 | *(reflection->MutableMessage(message, field_descriptor))); |
520 | | #else |
521 | | UNREFERENCED_PARAMETER(message); |
522 | | UNREFERENCED_PARAMETER(typed_message); |
523 | | UNREFERENCED_PARAMETER(reflection); |
524 | | UNREFERENCED_PARAMETER(field_descriptor); |
525 | | IS_ENVOY_BUG("redaction requested with JSON/YAML support removed"); |
526 | | #endif |
527 | 0 | }); |
528 | 1.10M | } |
529 | | |
530 | | // Recursive helper method for MessageUtil::redact() below. |
531 | 588k | void redact(Protobuf::Message* message, bool ancestor_is_sensitive) { |
532 | 588k | if (redactAny(message, ancestor_is_sensitive) || |
533 | 588k | redactTypedStruct(message, "xds.type.v3.TypedStruct", ancestor_is_sensitive) || |
534 | 588k | redactTypedStruct(message, "udpa.type.v1.TypedStruct", ancestor_is_sensitive)) { |
535 | 37.3k | return; |
536 | 37.3k | } |
537 | | |
538 | 551k | Protobuf::ReflectableMessage reflectable_message = createReflectableMessage(*message); |
539 | 551k | const auto* descriptor = reflectable_message->GetDescriptor(); |
540 | 551k | const auto* reflection = reflectable_message->GetReflection(); |
541 | 4.36M | for (int i = 0; i < descriptor->field_count(); ++i) { |
542 | 3.81M | const auto* field_descriptor = descriptor->field(i); |
543 | | |
544 | | // Redact if this field or any of its ancestors have the `sensitive` option set. |
545 | 3.81M | const bool sensitive = ancestor_is_sensitive || |
546 | 3.81M | field_descriptor->options().GetExtension(udpa::annotations::sensitive); |
547 | | |
548 | 3.81M | if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_MESSAGE) { |
549 | | // Recursive case: traverse message fields. |
550 | 1.71M | if (field_descriptor->is_map()) { |
551 | | // Redact values of maps only. Redacting both leaves the map with multiple "[redacted]" |
552 | | // keys. |
553 | 39.4k | const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor); |
554 | 59.7k | for (int i = 0; i < field_size; ++i) { |
555 | 20.3k | Protobuf::Message* map_pair_base = |
556 | 20.3k | reflection->MutableRepeatedMessage(&(*reflectable_message), field_descriptor, i); |
557 | 20.3k | Protobuf::ReflectableMessage map_pair = createReflectableMessage(*map_pair_base); |
558 | 20.3k | auto* value_field_desc = map_pair->GetDescriptor()->FindFieldByName("value"); |
559 | 20.3k | if (sensitive && (value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_STRING || |
560 | 0 | value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_BYTES)) { |
561 | 0 | map_pair->GetReflection()->SetString(&(*map_pair), value_field_desc, "[redacted]"); |
562 | 20.3k | } else if (value_field_desc->type() == Protobuf::FieldDescriptor::TYPE_MESSAGE) { |
563 | 20.3k | redact(map_pair->GetReflection()->MutableMessage(&(*map_pair), value_field_desc), |
564 | 20.3k | sensitive); |
565 | 20.3k | } else if (sensitive) { |
566 | 0 | map_pair->GetReflection()->ClearField(&(*map_pair), value_field_desc); |
567 | 0 | } |
568 | 20.3k | } |
569 | 1.67M | } else if (field_descriptor->is_repeated()) { |
570 | 256k | const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor); |
571 | 597k | for (int i = 0; i < field_size; ++i) { |
572 | 340k | redact(reflection->MutableRepeatedMessage(&(*reflectable_message), field_descriptor, i), |
573 | 340k | sensitive); |
574 | 340k | } |
575 | 1.42M | } else if (reflection->HasField(*reflectable_message, field_descriptor)) { |
576 | 184k | redact(reflection->MutableMessage(&(*reflectable_message), field_descriptor), sensitive); |
577 | 184k | } |
578 | 2.09M | } else if (sensitive) { |
579 | | // Base case: replace strings and bytes with "[redacted]" and clear all others. |
580 | 63.2k | if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_STRING || |
581 | 63.2k | field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_BYTES) { |
582 | 63.2k | if (field_descriptor->is_repeated()) { |
583 | 0 | const int field_size = reflection->FieldSize(*reflectable_message, field_descriptor); |
584 | 0 | for (int i = 0; i < field_size; ++i) { |
585 | 0 | reflection->SetRepeatedString(&(*reflectable_message), field_descriptor, i, |
586 | 0 | "[redacted]"); |
587 | 0 | } |
588 | 63.2k | } else if (reflection->HasField(*reflectable_message, field_descriptor)) { |
589 | 15.8k | reflection->SetString(&(*reflectable_message), field_descriptor, "[redacted]"); |
590 | 15.8k | } |
591 | 63.2k | } else { |
592 | 0 | reflection->ClearField(&(*reflectable_message), field_descriptor); |
593 | 0 | } |
594 | 63.2k | } |
595 | 3.81M | } |
596 | 551k | } |
597 | | |
598 | | } // namespace |
599 | | |
600 | 5.28k | void MessageUtil::redact(Protobuf::Message& message) { |
601 | 5.28k | ::Envoy::redact(&message, /* ancestor_is_sensitive = */ false); |
602 | 5.28k | } |
603 | | |
604 | 310 | void MessageUtil::wireCast(const Protobuf::Message& src, Protobuf::Message& dst) { |
605 | | // This should should generally succeed, but if there are malformed UTF-8 strings in a message, |
606 | | // this can fail. |
607 | 310 | if (!dst.ParseFromString(src.SerializeAsString())) { |
608 | 0 | throwEnvoyExceptionOrPanic("Unable to deserialize during wireCast()"); |
609 | 0 | } |
610 | 310 | } |
611 | | |
612 | 0 | bool ValueUtil::equal(const ProtobufWkt::Value& v1, const ProtobufWkt::Value& v2) { |
613 | 0 | ProtobufWkt::Value::KindCase kind = v1.kind_case(); |
614 | 0 | if (kind != v2.kind_case()) { |
615 | 0 | return false; |
616 | 0 | } |
617 | | |
618 | 0 | switch (kind) { |
619 | 0 | case ProtobufWkt::Value::KIND_NOT_SET: |
620 | 0 | return v2.kind_case() == ProtobufWkt::Value::KIND_NOT_SET; |
621 | | |
622 | 0 | case ProtobufWkt::Value::kNullValue: |
623 | 0 | return true; |
624 | | |
625 | 0 | case ProtobufWkt::Value::kNumberValue: |
626 | 0 | return v1.number_value() == v2.number_value(); |
627 | | |
628 | 0 | case ProtobufWkt::Value::kStringValue: |
629 | 0 | return v1.string_value() == v2.string_value(); |
630 | | |
631 | 0 | case ProtobufWkt::Value::kBoolValue: |
632 | 0 | return v1.bool_value() == v2.bool_value(); |
633 | | |
634 | 0 | case ProtobufWkt::Value::kStructValue: { |
635 | 0 | const ProtobufWkt::Struct& s1 = v1.struct_value(); |
636 | 0 | const ProtobufWkt::Struct& s2 = v2.struct_value(); |
637 | 0 | if (s1.fields_size() != s2.fields_size()) { |
638 | 0 | return false; |
639 | 0 | } |
640 | 0 | for (const auto& it1 : s1.fields()) { |
641 | 0 | const auto& it2 = s2.fields().find(it1.first); |
642 | 0 | if (it2 == s2.fields().end()) { |
643 | 0 | return false; |
644 | 0 | } |
645 | | |
646 | 0 | if (!equal(it1.second, it2->second)) { |
647 | 0 | return false; |
648 | 0 | } |
649 | 0 | } |
650 | 0 | return true; |
651 | 0 | } |
652 | | |
653 | 0 | case ProtobufWkt::Value::kListValue: { |
654 | 0 | const ProtobufWkt::ListValue& l1 = v1.list_value(); |
655 | 0 | const ProtobufWkt::ListValue& l2 = v2.list_value(); |
656 | 0 | if (l1.values_size() != l2.values_size()) { |
657 | 0 | return false; |
658 | 0 | } |
659 | 0 | for (int i = 0; i < l1.values_size(); i++) { |
660 | 0 | if (!equal(l1.values(i), l2.values(i))) { |
661 | 0 | return false; |
662 | 0 | } |
663 | 0 | } |
664 | 0 | return true; |
665 | 0 | } |
666 | 0 | } |
667 | 0 | return false; |
668 | 0 | } |
669 | | |
670 | 43.0k | const ProtobufWkt::Value& ValueUtil::nullValue() { |
671 | 43.0k | static const auto* v = []() -> ProtobufWkt::Value* { |
672 | 4 | auto* vv = new ProtobufWkt::Value(); |
673 | 4 | vv->set_null_value(ProtobufWkt::NULL_VALUE); |
674 | 4 | return vv; |
675 | 4 | }(); |
676 | 43.0k | return *v; |
677 | 43.0k | } |
678 | | |
679 | 486k | ProtobufWkt::Value ValueUtil::stringValue(const std::string& str) { |
680 | 486k | ProtobufWkt::Value val; |
681 | 486k | val.set_string_value(str); |
682 | 486k | return val; |
683 | 486k | } |
684 | | |
685 | 0 | ProtobufWkt::Value ValueUtil::optionalStringValue(const absl::optional<std::string>& str) { |
686 | 0 | if (str.has_value()) { |
687 | 0 | return ValueUtil::stringValue(str.value()); |
688 | 0 | } |
689 | 0 | return ValueUtil::nullValue(); |
690 | 0 | } |
691 | | |
692 | 2.66k | ProtobufWkt::Value ValueUtil::boolValue(bool b) { |
693 | 2.66k | ProtobufWkt::Value val; |
694 | 2.66k | val.set_bool_value(b); |
695 | 2.66k | return val; |
696 | 2.66k | } |
697 | | |
698 | 18.2k | ProtobufWkt::Value ValueUtil::structValue(const ProtobufWkt::Struct& obj) { |
699 | 18.2k | ProtobufWkt::Value val; |
700 | 18.2k | (*val.mutable_struct_value()) = obj; |
701 | 18.2k | return val; |
702 | 18.2k | } |
703 | | |
704 | 10.0k | ProtobufWkt::Value ValueUtil::listValue(const std::vector<ProtobufWkt::Value>& values) { |
705 | 10.0k | auto list = std::make_unique<ProtobufWkt::ListValue>(); |
706 | 25.3k | for (const auto& value : values) { |
707 | 25.3k | *list->add_values() = value; |
708 | 25.3k | } |
709 | 10.0k | ProtobufWkt::Value val; |
710 | 10.0k | val.set_allocated_list_value(list.release()); |
711 | 10.0k | return val; |
712 | 10.0k | } |
713 | | |
714 | | namespace { |
715 | | |
716 | | absl::Status validateDurationNoThrow(const ProtobufWkt::Duration& duration, |
717 | 512k | int64_t max_seconds_value) { |
718 | 512k | if (duration.seconds() < 0 || duration.nanos() < 0) { |
719 | 33.2k | return absl::OutOfRangeError( |
720 | 33.2k | fmt::format("Expected positive duration: {}", duration.DebugString())); |
721 | 33.2k | } |
722 | 479k | if (duration.nanos() > 999999999 || duration.seconds() > max_seconds_value) { |
723 | 13.3k | return absl::OutOfRangeError(fmt::format("Duration out-of-range: {}", duration.DebugString())); |
724 | 13.3k | } |
725 | 466k | return absl::OkStatus(); |
726 | 479k | } |
727 | | |
728 | 86.9k | void validateDuration(const ProtobufWkt::Duration& duration, int64_t max_seconds_value) { |
729 | 86.9k | const auto result = validateDurationNoThrow(duration, max_seconds_value); |
730 | 86.9k | if (!result.ok()) { |
731 | 132 | throwExceptionOrPanic(DurationUtil::OutOfRangeException, std::string(result.message())); |
732 | 132 | } |
733 | 86.9k | } |
734 | | |
735 | 5.65k | void validateDuration(const ProtobufWkt::Duration& duration) { |
736 | 5.65k | validateDuration(duration, Protobuf::util::TimeUtil::kDurationMaxSeconds); |
737 | 5.65k | } |
738 | | |
739 | 81.3k | void validateDurationAsMilliseconds(const ProtobufWkt::Duration& duration) { |
740 | | // Apply stricter max boundary to the `seconds` value to avoid overflow. |
741 | | // Note that protobuf internally converts to nanoseconds. |
742 | | // The kMaxInt64Nanoseconds = 9223372036, which is about 300 years. |
743 | 81.3k | constexpr int64_t kMaxInt64Nanoseconds = |
744 | 81.3k | std::numeric_limits<int64_t>::max() / (1000 * 1000 * 1000); |
745 | 81.3k | validateDuration(duration, kMaxInt64Nanoseconds); |
746 | 81.3k | } |
747 | | |
748 | 425k | absl::Status validateDurationAsMillisecondsNoThrow(const ProtobufWkt::Duration& duration) { |
749 | 425k | constexpr int64_t kMaxInt64Nanoseconds = |
750 | 425k | std::numeric_limits<int64_t>::max() / (1000 * 1000 * 1000); |
751 | 425k | return validateDurationNoThrow(duration, kMaxInt64Nanoseconds); |
752 | 425k | } |
753 | | |
754 | | } // namespace |
755 | | |
756 | 81.3k | uint64_t DurationUtil::durationToMilliseconds(const ProtobufWkt::Duration& duration) { |
757 | 81.3k | validateDurationAsMilliseconds(duration); |
758 | 81.3k | return Protobuf::util::TimeUtil::DurationToMilliseconds(duration); |
759 | 81.3k | } |
760 | | |
761 | | absl::StatusOr<uint64_t> |
762 | 425k | DurationUtil::durationToMillisecondsNoThrow(const ProtobufWkt::Duration& duration) { |
763 | 425k | const auto result = validateDurationAsMillisecondsNoThrow(duration); |
764 | 425k | if (!result.ok()) { |
765 | 46.5k | return result; |
766 | 46.5k | } |
767 | 379k | return Protobuf::util::TimeUtil::DurationToMilliseconds(duration); |
768 | 425k | } |
769 | | |
770 | 5.65k | uint64_t DurationUtil::durationToSeconds(const ProtobufWkt::Duration& duration) { |
771 | 5.65k | validateDuration(duration); |
772 | 5.65k | return Protobuf::util::TimeUtil::DurationToSeconds(duration); |
773 | 5.65k | } |
774 | | |
775 | | void TimestampUtil::systemClockToTimestamp(const SystemTime system_clock_time, |
776 | 12.1k | ProtobufWkt::Timestamp& timestamp) { |
777 | | // Converts to millisecond-precision Timestamp by explicitly casting to millisecond-precision |
778 | | // time_point. |
779 | 12.1k | timestamp.MergeFrom(Protobuf::util::TimeUtil::MillisecondsToTimestamp( |
780 | 12.1k | std::chrono::time_point_cast<std::chrono::milliseconds>(system_clock_time) |
781 | 12.1k | .time_since_epoch() |
782 | 12.1k | .count())); |
783 | 12.1k | } |
784 | | |
785 | 349k | absl::string_view TypeUtil::typeUrlToDescriptorFullName(absl::string_view type_url) { |
786 | 349k | const size_t pos = type_url.rfind('/'); |
787 | 349k | if (pos != absl::string_view::npos) { |
788 | 326k | type_url = type_url.substr(pos + 1); |
789 | 326k | } |
790 | 349k | return type_url; |
791 | 349k | } |
792 | | |
793 | 1.74k | std::string TypeUtil::descriptorFullNameToTypeUrl(absl::string_view type) { |
794 | 1.74k | return "type.googleapis.com/" + std::string(type); |
795 | 1.74k | } |
796 | | |
797 | 4 | void StructUtil::update(ProtobufWkt::Struct& obj, const ProtobufWkt::Struct& with) { |
798 | 4 | auto& obj_fields = *obj.mutable_fields(); |
799 | | |
800 | 4 | for (const auto& [key, val] : with.fields()) { |
801 | 1 | auto& obj_key = obj_fields[key]; |
802 | | |
803 | | // If the types are different, the last one wins. |
804 | 1 | const auto val_kind = val.kind_case(); |
805 | 1 | if (val_kind != obj_key.kind_case()) { |
806 | 1 | obj_key = val; |
807 | 1 | continue; |
808 | 1 | } |
809 | | |
810 | | // Otherwise, the strategy depends on the value kind. |
811 | 0 | switch (val.kind_case()) { |
812 | | // For scalars, the last one wins. |
813 | 0 | case ProtobufWkt::Value::kNullValue: |
814 | 0 | case ProtobufWkt::Value::kNumberValue: |
815 | 0 | case ProtobufWkt::Value::kStringValue: |
816 | 0 | case ProtobufWkt::Value::kBoolValue: |
817 | 0 | obj_key = val; |
818 | 0 | break; |
819 | | // If we got a structure, recursively update. |
820 | 0 | case ProtobufWkt::Value::kStructValue: |
821 | 0 | update(*obj_key.mutable_struct_value(), val.struct_value()); |
822 | 0 | break; |
823 | | // For lists, append the new values. |
824 | 0 | case ProtobufWkt::Value::kListValue: { |
825 | 0 | auto& obj_key_vec = *obj_key.mutable_list_value()->mutable_values(); |
826 | 0 | const auto& vals = val.list_value().values(); |
827 | 0 | obj_key_vec.MergeFrom(vals); |
828 | 0 | break; |
829 | 0 | } |
830 | 0 | case ProtobufWkt::Value::KIND_NOT_SET: |
831 | 0 | break; |
832 | 0 | } |
833 | 0 | } |
834 | 4 | } |
835 | | |
836 | | } // namespace Envoy |