Skip to content

Commit

Permalink
hcm: support raw header value
Browse files Browse the repository at this point in the history
Signed-off-by: Kuat Yessenov <[email protected]>
  • Loading branch information
kyessenov committed Sep 14, 2023
1 parent 4026d66 commit 0fc8d8c
Show file tree
Hide file tree
Showing 6 changed files with 54 additions and 3 deletions.
3 changes: 3 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ new_features:
change: |
Added the ability to specify a custom upstream local address selector using
:ref:`local_address_selector:<envoy_v3_api_field_config.core.v3.BindConfig.local_address_selector>`.
- area: http
change: |
added support for Base64 encoding of the raw header values in the custom header operations.
deprecated:
- area: tracing
Expand Down
3 changes: 3 additions & 0 deletions docs/root/configuration/http/http_conn_man/headers.rst
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,9 @@ is empty string. For example, the following configuration uses ``%RESPONSE_CODE%
modify request headers using code from the response. The output is an empty string, because request
headers are modified before the request is sent upstream and the response is not received yet.

Raw header values accept dynamic values as well and require escaping the
literal percent symbols. Raw header values are encoded as Base64 when appended.

.. literalinclude:: _include/header_formatters.yaml
:language: yaml
:linenos:
Expand Down
1 change: 1 addition & 0 deletions source/common/router/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ envoy_cc_library(
"//envoy/router:string_accessor_interface",
"//envoy/stream_info:filter_state_interface",
"//envoy/stream_info:stream_info_interface",
"//source/common/common:base64_lib",
"//source/common/common:minimal_logger_lib",
"//source/common/common:utility_lib",
"//source/common/config:metadata_lib",
Expand Down
8 changes: 7 additions & 1 deletion source/common/router/header_formatter.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

#include "envoy/formatter/substitution_formatter.h"

#include "source/common/common/base64.h"
#include "source/common/http/header_map_impl.h"

#include "absl/container/node_hash_map.h"
Expand Down Expand Up @@ -34,7 +35,8 @@ using HttpHeaderFormatterPtr = std::unique_ptr<HttpHeaderFormatter>;
*/
class HttpHeaderFormatterImpl : public HttpHeaderFormatter {
public:
HttpHeaderFormatterImpl(Formatter::FormatterPtr&& formatter) : formatter_(std::move(formatter)) {}
HttpHeaderFormatterImpl(Formatter::FormatterPtr&& formatter, bool raw_value)
: formatter_(std::move(formatter)), raw_value_(raw_value) {}

// HttpHeaderFormatter::format
// Trailers are not available when HTTP headers are manipulated.
Expand All @@ -43,11 +45,15 @@ class HttpHeaderFormatterImpl : public HttpHeaderFormatter {
const Envoy::StreamInfo::StreamInfo& stream_info) const override {
std::string buf;
buf = formatter_->formatWithContext({&request_headers, &response_headers}, stream_info);
if (raw_value_) {
return Base64::encode(buf.data(), buf.size());
}
return buf;
};

private:
const Formatter::FormatterPtr formatter_;
const bool raw_value_;
};

} // namespace Router
Expand Down
9 changes: 7 additions & 2 deletions source/common/router/header_parser.cc
Original file line number Diff line number Diff line change
Expand Up @@ -37,16 +37,21 @@ parseHttpHeaderFormatter(const envoy::config::core::v3::HeaderValue& header_valu
if (!Http::HeaderUtility::isModifiableHeader(key)) {
throw EnvoyException(":-prefixed or host headers may not be modified");
}
const bool raw_value = !header_value.raw_value().empty();
if (raw_value && !header_value.value().empty()) {
throw EnvoyException("cannot specify both value and raw_value");
}

// UPSTREAM_METADATA and DYNAMIC_METADATA must be translated from JSON ["a", "b"] format to colon
// format (a:b)
std::string final_header_value = HeaderParser::translateMetadataFormat(header_value.value());
std::string final_header_value = HeaderParser::translateMetadataFormat(
raw_value ? header_value.raw_value() : header_value.value());
// Change PER_REQUEST_STATE to FILTER_STATE.
final_header_value = HeaderParser::translatePerRequestState(final_header_value);

// Let the substitution formatter parse the final_header_value.
return std::make_unique<HttpHeaderFormatterImpl>(
std::make_unique<Envoy::Formatter::FormatterImpl>(final_header_value, true));
std::make_unique<Envoy::Formatter::FormatterImpl>(final_header_value, true), raw_value);
}

} // namespace
Expand Down
33 changes: 33 additions & 0 deletions test/common/router/header_formatter_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -655,6 +655,39 @@ match: { prefix: "/new_endpoint" }
EXPECT_EQ(1, counts["x-request-start"]);
}

TEST(HeaderParserTest, EvaluateRawHeader) {
// The encoded value is: %FILTER_STATE(test_key:PLAIN)%
const std::string yaml = R"EOF(
match: { prefix: "/new_endpoint" }
route:
cluster: www2
request_headers_to_add:
- header:
key: "x-per-request"
raw_value: JUZJTFRFUl9TVEFURSh0ZXN0X2tleTpQTEFJTiklCg==
)EOF";

const auto route = parseRouteFromV3Yaml(yaml);
HeaderParserPtr req_header_parser =
HeaderParser::configure(route.request_headers_to_add(), route.request_headers_to_remove());
Http::TestRequestHeaderMapImpl header_map{{":method", "POST"}};
NiceMock<Envoy::StreamInfo::MockStreamInfo> stream_info;

Envoy::StreamInfo::FilterStateSharedPtr filter_state(
std::make_shared<Envoy::StreamInfo::FilterStateImpl>(
Envoy::StreamInfo::FilterState::LifeSpan::FilterChain));
filter_state->setData("test_key", std::make_unique<StringAccessorImpl>("test_value"),
StreamInfo::FilterState::StateType::ReadOnly,
StreamInfo::FilterState::LifeSpan::FilterChain);
ON_CALL(stream_info, filterState()).WillByDefault(ReturnRef(filter_state));
ON_CALL(Const(stream_info), filterState()).WillByDefault(ReturnRef(*filter_state));

req_header_parser->evaluateHeaders(header_map, stream_info);

EXPECT_TRUE(header_map.has("x-per-request"));
EXPECT_EQ("dGVzdF92YWx1ZQo=", header_map.get_("x-per-request"));
}

TEST(HeaderParserTest, EvaluateResponseHeaders) {
const std::string yaml = R"EOF(
match: { prefix: "/new_endpoint" }
Expand Down

0 comments on commit 0fc8d8c

Please sign in to comment.