Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ message Config {
enum ValueType {
STRING = 0;
NUMBER = 1;

// `Base64Url <https://tools.ietf.org/html/rfc4648#section-5>`_ encoded string of
// serialized `protobuf.Value
// <https://github.com/protocolbuffers/protobuf/blob/master/src/google/protobuf/struct.proto#L62>`_
BASE64URL_SERIALIZED = 2;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This is counter intuitive from API stand point without reading the comment above, from reading the implementation, I'd say make this VALUE or PROTOBUF_VALUE, and have another enum for the encoding to express it, since which base64 or not is orthogonal to value type here, WDYT?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That looks a cleaner API though I imagine the base64 option is likely to be always used only with PROTOBUF_VALUE?

I'm fine with the suggestion, @rgs1 WDYT?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is possible for someone to use STRING with BASE64 with some non header safe character sets (UTF-8), and when you add more binary types here it will help anyway.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

yeah i like Lizan's suggestion (PROTOBUF_VALUE).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, updated.

}

message KeyValuePair {
Expand Down
1 change: 1 addition & 0 deletions docs/root/intro/version_history.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Version history
* config: changed the default value of :ref:`initial_fetch_timeout <envoy_api_field_core.ConfigSource.initial_fetch_timeout>` from 0s to 15s. This is a change in behaviour in the sense that Envoy will move to the next initialization phase, even if the first config is not delivered in 15s. Refer to :ref:`initialization process <arch_overview_initialization>` for more details.
* fault: added overrides for default runtime keys in :ref:`HTTPFault <envoy_api_msg_config.filter.http.fault.v2.HTTPFault>` filter.
* grpc-json: added support for :ref:`ignoring unknown query parameters<envoy_api_field_config.filter.http.transcoder.v2.GrpcJsonTranscoder.ignore_unknown_query_parameters>`.
* header to metadata: added support for :ref:`Base64Url serialized type<envoy_api_enum_value_config.filter.http.header_to_metadata.v2.Config.ValueType.BASE64URL_SERIALIZED>`.
* http: added the ability to reject HTTP/1.1 requests with invalid HTTP header values, using the runtime feature `envoy.reloadable_features.strict_header_validation`.
* http: added the ability to :ref:`merge adjacent slashes<envoy_api_field_config.filter.network.http_connection_manager.v2.HttpConnectionManager.merge_slashes>` in the path.
* listeners: added :ref:`HTTP inspector listener filter <config_listener_filters_http_inspector>`.
Expand Down
1 change: 1 addition & 0 deletions source/extensions/filters/http/header_to_metadata/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ envoy_cc_library(
hdrs = ["header_to_metadata_filter.h"],
deps = [
"//include/envoy/server:filter_config_interface",
"//source/common/common:base64_lib",
"//source/extensions/filters/http:well_known_names",
"@envoy_api//envoy/config/filter/http/header_to_metadata/v2:header_to_metadata_cc",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "extensions/filters/http/header_to_metadata/header_to_metadata_filter.h"

#include "common/common/base64.h"
#include "common/config/well_known_names.h"
#include "common/protobuf/protobuf.h"

Expand All @@ -14,7 +15,7 @@ namespace HttpFilters {
namespace HeaderToMetadataFilter {
namespace {

const uint32_t MAX_HEADER_VALUE_LEN = 100;
const uint32_t MAX_HEADER_VALUE_LEN = 10 * 1024;
Comment thread
yangminzhu marked this conversation as resolved.
Outdated

} // namespace

Expand Down Expand Up @@ -113,6 +114,18 @@ bool HeaderToMetadataFilter::addMetadata(StructMap& map, const std::string& meta
}
break;
}
case envoy::config::filter::http::header_to_metadata::v2::Config_ValueType_BASE64URL_SERIALIZED: {
const auto decoded = Base64Url::decode(std::string(value));
Comment thread
yangminzhu marked this conversation as resolved.
Outdated
if (decoded.empty()) {
ENVOY_LOG(debug, "Base64Url decode failed");
return false;
}
if (!val.ParseFromString(decoded)) {
ENVOY_LOG(debug, "parse from decoded string failed");
return false;
}
break;
}
default:
ENVOY_LOG(debug, "unknown value type");
return false;
Expand Down
1 change: 1 addition & 0 deletions test/extensions/filters/http/header_to_metadata/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ envoy_extension_cc_test(
srcs = ["header_to_metadata_filter_test.cc"],
extension_name = "envoy.filters.http.header_to_metadata",
deps = [
"//source/common/common:base64_lib",
"//source/extensions/filters/http/header_to_metadata:header_to_metadata_filter_lib",
"//test/mocks/server:server_mocks",
],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#include "common/common/base64.h"
#include "common/http/header_map_impl.h"
#include "common/protobuf/protobuf.h"

#include "extensions/filters/http/header_to_metadata/header_to_metadata_filter.h"

Expand Down Expand Up @@ -68,6 +70,15 @@ MATCHER_P(MapEqNum, rhs, "") {
return true;
}

MATCHER_P(MapEqValue, rhs, "") {
const ProtobufWkt::Struct& obj = arg;
EXPECT_TRUE(!rhs.empty());
for (auto const& entry : rhs) {
EXPECT_TRUE(TestUtility::protoEqual(obj.fields().at(entry.first), entry.second));
}
return true;
}

/**
* Basic use-case.
*/
Expand Down Expand Up @@ -154,6 +165,82 @@ TEST_F(HeaderToMetadataTest, NumberTypeTest) {
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(incoming_headers, false));
}

/**
* Test the value gets written as a Base64Url serialized type.
*/
TEST_F(HeaderToMetadataTest, Base64UrlSerializedTypeTest) {
const std::string response_config_yaml = R"EOF(
response_rules:
- header: x-authenticated
on_header_present:
key: auth
type: BASE64URL_SERIALIZED
)EOF";
initializeFilter(response_config_yaml);

ProtobufWkt::Value value;
auto* s = value.mutable_struct_value();

ProtobufWkt::Value v;
v.set_string_value("blafoo");
(*s->mutable_fields())["k1"] = v;
v.set_number_value(2019.07);
(*s->mutable_fields())["k2"] = v;
v.set_bool_value(true);
(*s->mutable_fields())["k3"] = v;

std::string output;
ASSERT_TRUE(value.SerializeToString(&output));
const auto encoded = Base64Url::encode(output.c_str(), output.size());
Http::TestHeaderMapImpl incoming_headers{{"x-authenticated", encoded}};
std::map<std::string, ProtobufWkt::Value> expected = {{"auth", value}};

EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(req_info_));
EXPECT_CALL(req_info_,
setDynamicMetadata("envoy.filters.http.header_to_metadata", MapEqValue(expected)));
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(incoming_headers, false));
}

/**
* Base64Url serialized type with invalid Base64Url encoding.
*/
TEST_F(HeaderToMetadataTest, Base64UrlSerializedTypeInvalidBase64UrlTest) {
const std::string response_config_yaml = R"EOF(
response_rules:
- header: x-authenticated
on_header_present:
key: auth
type: BASE64URL_SERIALIZED
)EOF";
initializeFilter(response_config_yaml);
Http::TestHeaderMapImpl incoming_headers{{"x-authenticated", "invalid"}};

EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(req_info_));
EXPECT_CALL(req_info_, setDynamicMetadata(_, _)).Times(0);
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(incoming_headers, false));
}

/**
* Base64Url serialized type with invalid serialized protobuf.
*/
TEST_F(HeaderToMetadataTest, Base64UrlSerializedTypeInvalidProtobufTest) {
const std::string response_config_yaml = R"EOF(
response_rules:
- header: x-authenticated
on_header_present:
key: auth
type: BASE64URL_SERIALIZED
)EOF";
initializeFilter(response_config_yaml);
std::string output = "invalid";
const auto encoded = Base64Url::encode(output.c_str(), output.size());
Http::TestHeaderMapImpl incoming_headers{{"x-authenticated", encoded}};

EXPECT_CALL(encoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(req_info_));
EXPECT_CALL(req_info_, setDynamicMetadata(_, _)).Times(0);
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->encodeHeaders(incoming_headers, false));
}

/**
* Headers not present.
*/
Expand Down Expand Up @@ -221,7 +308,7 @@ TEST_F(HeaderToMetadataTest, EmptyHeaderValue) {
*/
TEST_F(HeaderToMetadataTest, HeaderValueTooLong) {
initializeFilter(request_config_yaml);
Http::TestHeaderMapImpl incoming_headers{{"X-VERSION", std::string(101, 'x')}};
Http::TestHeaderMapImpl incoming_headers{{"X-VERSION", std::string(10 * 1024 + 1, 'x')}};

EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(req_info_));
EXPECT_CALL(req_info_, setDynamicMetadata(_, _)).Times(0);
Expand Down