Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
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 @@ -86,7 +86,7 @@ message Config {
}

// A Rule defines what metadata to apply when a header is present or missing.
// [#next-free-field: 6]
// [#next-free-field: 7]
message Rule {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.http.header_to_metadata.v2.Config.Rule";
Expand Down Expand Up @@ -122,6 +122,10 @@ message Config {
// This prevents headers from leaking.
// This field is not supported in case of a cookie.
bool remove = 4;

// If this is set, the applied metadata value will be used as one of the SANs to validate the
// certificate in the upstream connection. This is used to override the SAN in the TLS context.
bool use_as_upstream_subject_alt_name = 6;
}

// The list of rules to apply to requests.
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 @@ -22,6 +22,7 @@ envoy_cc_library(
"//source/common/config:well_known_names",
"//source/common/http:header_utility_lib",
"//source/common/http:utility_lib",
"//source/common/network:upstream_subject_alt_names_lib",
"@envoy_api//envoy/extensions/filters/http/header_to_metadata/v3:pkg_cc_proto",
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "source/common/config/well_known_names.h"
#include "source/common/http/header_utility.h"
#include "source/common/http/utility.h"
#include "source/common/network/upstream_subject_alt_names.h"
#include "source/common/protobuf/protobuf.h"

#include "absl/strings/numbers.h"
Expand Down Expand Up @@ -216,8 +217,8 @@ const std::string& HeaderToMetadataFilter::decideNamespace(const std::string& ns
}

// add metadata['key']= value depending on header present or missing case
void HeaderToMetadataFilter::applyKeyValue(std::string&& value, const Rule& rule,
const KeyValuePair& keyval, StructMap& np) {
std::string HeaderToMetadataFilter::applyKeyValue(std::string&& value, const Rule& rule,
const KeyValuePair& keyval, StructMap& np) {
if (!keyval.value().empty()) {
value = keyval.value();
} else {
Expand All @@ -232,31 +233,46 @@ void HeaderToMetadataFilter::applyKeyValue(std::string&& value, const Rule& rule
} else {
ENVOY_LOG(debug, "value is empty, not adding metadata");
}

return std::move(value);
}

void HeaderToMetadataFilter::writeHeaderToMetadata(Http::HeaderMap& headers,
const HeaderToMetadataRules& rules,
Http::StreamFilterCallbacks& callbacks) {
StructMap structs_by_namespace;
std::vector<std::string> upstream_subject_alt_names{};

for (const auto& rule : rules) {
const auto& proto_rule = rule.rule();
absl::optional<std::string> value = rule.selector_->extract(headers);

if (value && proto_rule.has_on_header_present()) {
applyKeyValue(std::move(value).value_or(""), rule, proto_rule.on_header_present(),
structs_by_namespace);
auto applied_value = applyKeyValue(std::move(value).value_or(""), rule,
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.

Do we need to use value_or() here?

proto_rule.on_header_present(), structs_by_namespace);
if (!applied_value.empty() && proto_rule.use_as_upstream_subject_alt_name()) {
upstream_subject_alt_names.push_back(std::move(applied_value));
}
} else if (!value && proto_rule.has_on_header_missing()) {
applyKeyValue(std::move(value).value_or(""), rule, proto_rule.on_header_missing(),
structs_by_namespace);
}
}

auto& stream_info = callbacks.streamInfo();

// Any matching rules?
if (!structs_by_namespace.empty()) {
for (auto const& entry : structs_by_namespace) {
callbacks.streamInfo().setDynamicMetadata(entry.first, entry.second);
stream_info.setDynamicMetadata(entry.first, entry.second);
}
}

if (!upstream_subject_alt_names.empty()) {
auto data = std::make_unique<Network::UpstreamSubjectAltNames>(upstream_subject_alt_names);
stream_info.filterState()->setData(Network::UpstreamSubjectAltNames::key(), std::move(data),
StreamInfo::FilterState::StateType::Mutable);
}
}

// TODO(rgs1): this belongs in one of the filter interfaces, see issue #10164.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ class HeaderToMetadataFilter : public Http::StreamFilter,
Http::StreamFilterCallbacks& callbacks);
bool addMetadata(StructMap&, const std::string&, const std::string&, std::string, ValueType,
ValueEncode) const;
void applyKeyValue(std::string&&, const Rule&, const KeyValuePair&, StructMap&);
std::string applyKeyValue(std::string&&, const Rule&, const KeyValuePair&, StructMap&);
const std::string& decideNamespace(const std::string& nspace) const;
const Config* getConfig() const;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#include "source/common/common/base64.h"
#include "source/common/http/header_map_impl.h"
#include "source/common/network/upstream_subject_alt_names.h"
#include "source/common/protobuf/protobuf.h"
#include "source/extensions/filters/http/header_to_metadata/header_to_metadata_filter.h"

Expand Down Expand Up @@ -587,6 +588,65 @@ TEST_F(HeaderToMetadataTest, RegexSubstitution) {
}
}

/**
* Regex substitution and use as upstream SAN.
*/
TEST_F(HeaderToMetadataTest, RegexSubstitutionUpstreamSAN) {
const std::string config = R"EOF(
request_rules:
- header: :path
use_as_upstream_subject_alt_name: true
on_header_present:
metadata_namespace: envoy.lb
key: cluster
regex_value_rewrite:
pattern:
google_re2: {}
regex: "^/(cluster[\\d\\w-]+)/?.*$"
substitution: "\\1-san"
)EOF";
initializeFilter(config);

Http::TestRequestHeaderMapImpl headers{{":path", "/cluster-prod-001/x/y"}};
std::map<std::string, std::string> expected = {{"cluster", "cluster-prod-001-san"}};

EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(req_info_));
EXPECT_CALL(req_info_, setDynamicMetadata("envoy.lb", MapEq(expected)));
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false));
EXPECT_EQ("cluster-prod-001-san", req_info_.filterState()
->getDataReadOnly<Network::UpstreamSubjectAltNames>(
Network::UpstreamSubjectAltNames::key())
.value()[0]);
}

/**
* Regex substitution and use as upstream SAN: no match.
*/
TEST_F(HeaderToMetadataTest, RegexSubstitutionUpstreamSANNoMatch) {
const std::string config = R"EOF(
request_rules:
- header: x-use-as-upstream-san
use_as_upstream_subject_alt_name: true
on_header_present:
metadata_namespace: envoy.lb
key: cluster
regex_value_rewrite:
pattern:
google_re2: {}
regex: "^/(cluster[\\d\\w-]+)/?.*$"
substitution: "\\1-san"
)EOF";
initializeFilter(config);

Http::TestRequestHeaderMapImpl headers{{":path", "/foo"}};

EXPECT_CALL(decoder_callbacks_, streamInfo()).WillRepeatedly(ReturnRef(req_info_));
EXPECT_CALL(req_info_, setDynamicMetadata(_, _)).Times(0);
EXPECT_EQ(Http::FilterHeadersStatus::Continue, filter_->decodeHeaders(headers, false));
EXPECT_FALSE(req_info_.filterState()->hasData<Network::UpstreamSubjectAltNames>(
Network::UpstreamSubjectAltNames::key()));
}

/**
* Missing case is not executed when header is present.
*/
Expand Down