-
Notifications
You must be signed in to change notification settings - Fork 5.5k
api: add 'redacted' option for protobuf messages, and redact SSL certs #9315
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
e95cafb
794aa85
4948c4b
927d875
848b869
cf6ebc2
2f261f6
186a8b3
bd56dba
782d092
0f493fa
8013dbe
3552086
b4c9bf9
af5a7df
32673ff
09d70fc
11f9d43
2bf220f
f6e49c6
d9e836c
5dc3c06
29a6172
403fdd2
208e9b4
77f295b
2488863
769e817
891da34
e996e29
b690640
e77cb8f
7a6b20c
f8167a0
5f1211e
358bac9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,8 @@ | |
| #include "common/protobuf/protobuf.h" | ||
|
|
||
| #include "absl/strings/match.h" | ||
| #include "udpa/annotations/sensitive.pb.h" | ||
| #include "udpa/type/v1/typed_struct.pb.h" | ||
| #include "yaml-cpp/yaml.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
@@ -506,6 +508,114 @@ std::string MessageUtil::CodeEnumToString(ProtobufUtil::error::Code code) { | |
| } | ||
| } | ||
|
|
||
| namespace { | ||
|
|
||
| std::unique_ptr<Protobuf::Message> typeUrlToNewMessage(Protobuf::MessageFactory& message_factory, | ||
| absl::string_view type_url) { | ||
| const absl::string_view type_name = TypeUtil::typeUrlToDescriptorFullName(type_url); | ||
| const Protobuf::Descriptor* descriptor = | ||
| Protobuf::DescriptorPool::generated_pool()->FindMessageTypeByName( | ||
| static_cast<std::string>(type_name)); | ||
| return std::unique_ptr<Protobuf::Message>( | ||
| descriptor == nullptr ? nullptr : message_factory.GetPrototype(descriptor)->New()); | ||
| } | ||
|
|
||
| // Recursive helper method for MessageUtil::redact() below. Note that we have to keep track of | ||
| // whether an ancestor was marked as `sensitive`, not just the field, because of cases like | ||
| // `TlsContext::private_key`, which is of type `core.DataSource` rather than `string`. | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm do we want to redact if the DataSource is pointing to a file rather than inline string/bytes?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This approach doesn't give us any way to differentiate, unfortunately. |
||
| void redactInPlace(Protobuf::Message* message, bool ancestor_is_sensitive) { | ||
| // If the message is an `Any`, we have to first unpack it to its original type to redact it... | ||
| auto* any = dynamic_cast<ProtobufWkt::Any*>(message); | ||
| if (any != nullptr) { | ||
|
mergeconflict marked this conversation as resolved.
Outdated
|
||
| Protobuf::DynamicMessageFactory message_factory; | ||
| auto typed_message = typeUrlToNewMessage(message_factory, any->type_url()); | ||
| if (typed_message == nullptr) { | ||
| // If the type URL doesn't correspond to a known proto, give up redacting and treat this | ||
| // message the same as a `ProtobufWkt::Struct`. See the documented limitation on | ||
| // `MessageUtil::redact()` for more context. | ||
| ENVOY_LOG_MISC(warn, "Could not redact ProtobufWkt::Any with unknown type URL {}", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are there tests that check for this condition (ideally doing an
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are tests for this condition, although not using |
||
| any->type_url()); | ||
| return; | ||
| } | ||
|
|
||
| any->UnpackTo(typed_message.get()); | ||
| redactInPlace(typed_message.get(), ancestor_is_sensitive); | ||
| any->PackFrom(*typed_message); | ||
| return; | ||
| } | ||
|
|
||
| // If the message is a `TypedStruct`, it also contains a `type_url` and can be handled the same | ||
| // way as `Any` above. | ||
| auto* typed_struct = dynamic_cast<udpa::type::v1::TypedStruct*>(message); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure how safe this is in general. You can have a
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, they are of type
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done, this was a really important catch. PTAL, thanks! |
||
| if (typed_struct != nullptr) { | ||
| Protobuf::DynamicMessageFactory message_factory; | ||
| auto typed_message = typeUrlToNewMessage(message_factory, typed_struct->type_url()); | ||
| if (typed_message == nullptr) { | ||
| // If the type URL doesn't correspond to a known proto, give up redacting and treat this | ||
| // message the same as a `ProtobufWkt::Struct`. See the documented limitation on | ||
| // `MessageUtil::redact()` for more context. | ||
| ENVOY_LOG_MISC(warn, "Could not redact udpa::type::v1::TypedStruct with unknown type URL {}", | ||
|
mergeconflict marked this conversation as resolved.
Outdated
|
||
| typed_struct->type_url()); | ||
| return; | ||
| } | ||
|
|
||
| MessageUtil::jsonConvert(typed_struct->value(), ProtobufMessage::getNullValidationVisitor(), | ||
| *typed_message); | ||
| redactInPlace(typed_message.get(), ancestor_is_sensitive); | ||
| MessageUtil::jsonConvert(*typed_message, *(typed_struct->mutable_value())); | ||
| return; | ||
| } | ||
|
|
||
| // Otherwise, use reflection to traverse all populated fields of this message... | ||
|
mergeconflict marked this conversation as resolved.
Outdated
|
||
| const auto* reflection = message->GetReflection(); | ||
| std::vector<const Protobuf::FieldDescriptor*> field_descriptors; | ||
| reflection->ListFields(*message, &field_descriptors); | ||
|
mergeconflict marked this conversation as resolved.
Outdated
|
||
| for (const auto* field_descriptor : field_descriptors) { | ||
| // Redact if this field or any of its ancestors have the `sensitive` option set. | ||
| const bool sensitive = ancestor_is_sensitive || | ||
| field_descriptor->options().GetExtension(udpa::annotations::sensitive); | ||
|
|
||
| if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_MESSAGE) { | ||
| // Recursive case: traverse message fields. | ||
| if (field_descriptor->is_repeated()) { | ||
| const int field_size = reflection->FieldSize(*message, field_descriptor); | ||
| for (int i = 0; i < field_size; ++i) { | ||
| redactInPlace(reflection->MutableRepeatedMessage(message, field_descriptor, i), | ||
| sensitive); | ||
| } | ||
| } else { | ||
| redactInPlace(reflection->MutableMessage(message, field_descriptor), sensitive); | ||
| } | ||
| } else if (sensitive) { | ||
| // Base case: replace strings with "[redacted]" and clear all others. | ||
| if (field_descriptor->type() == Protobuf::FieldDescriptor::TYPE_STRING) { | ||
| if (field_descriptor->is_repeated()) { | ||
| const int field_size = reflection->FieldSize(*message, field_descriptor); | ||
| for (int i = 0; i < field_size; ++i) { | ||
| reflection->SetRepeatedString(message, field_descriptor, i, "[redacted]"); | ||
| } | ||
| } else { | ||
| reflection->SetString(message, field_descriptor, "[redacted]"); | ||
| } | ||
| } else { | ||
| reflection->ClearField(message, field_descriptor); | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| } // namespace | ||
|
|
||
| std::unique_ptr<Protobuf::Message> MessageUtil::redact(const Protobuf::Message& message) { | ||
| // 1. Clone the original message. New() will return an empty message of the correct dynamic type. | ||
|
mergeconflict marked this conversation as resolved.
Outdated
|
||
| std::unique_ptr<Protobuf::Message> redacted(message.New()); | ||
| redacted->MergeFrom(message); | ||
|
|
||
| // 2. Redact the cloned message. | ||
| redactInPlace(redacted.get(), /* ancestor_is_sensitive = */ false); | ||
| return redacted; | ||
| } | ||
|
|
||
| ProtobufWkt::Value ValueUtil::loadFromYaml(const std::string& yaml) { | ||
| try { | ||
| return parseYamlNode(YAML::Load(yaml)); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -541,9 +541,10 @@ Http::Code AdminImpl::handlerConfigDump(absl::string_view, Http::HeaderMap& resp | |
| auto& any_message = *(dump.add_configs()); | ||
| any_message.PackFrom(*message); | ||
| } | ||
| const auto redacted = MessageUtil::redact(dump); | ||
|
mergeconflict marked this conversation as resolved.
Outdated
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. Given the limitations that are now spelled out around Ultimately I'm wondering how we're going to document the effective result of this process in version history in a way that end users can grok and get predictable behavior from.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this is tough. On the one hand, usage of I think I'd like to move forward with this anyways, just so we can have something in place, but I definitely agree it's dissatisfying having this limitation.
mergeconflict marked this conversation as resolved.
Outdated
|
||
|
|
||
| response_headers.setReferenceContentType(Http::Headers::get().ContentTypeValues.Json); | ||
| response.add(MessageUtil::getJsonStringFromMessage(dump, true)); // pretty-print | ||
| response.add(MessageUtil::getJsonStringFromMessage(*redacted, true)); // pretty-print | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel we also want this in various logs, e.g. trace level xDS logs, where this has come up previously as an issue.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, definitely. I'm thinking to just target this one spot in this PR, as a proof of concept, and then track down others (e.g. #4757) as a follow-up.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, I'm good with keeping the issue open for follow-up. |
||
| return Http::Code::OK; | ||
| } | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.