Skip to content

Commit b41c87d

Browse files
EItanyasoloio-bulldozer[bot]
authored andcommitted
transformer header matchers (#31)
* added header matchers * git tests running * tested and working * got unit tests passig again with null route_config * integration tests work * integration tests now include a skip * fixed unit tests * Merge branch 'master' of github.com:solo-io/envoy-gloo into transformer-header-matchers * changelog * nearly switched over with yuvals help, now for tests * if no match is available, do not add transforms * no more seg faults * got tests passing again * integration tests passing * override * updated matcher code to be generic * made matchers common * udpated package * pr comments
1 parent 08f9360 commit b41c87d

File tree

13 files changed

+533
-161
lines changed

13 files changed

+533
-161
lines changed

api/envoy/config/filter/http/transformation/v2/BUILD

+7-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,10 @@ envoy_package()
99

1010
load("@envoy_api//bazel:api_build_system.bzl", "api_proto_package")
1111

12-
api_proto_package()
12+
api_proto_package(
13+
deps = [
14+
"@envoy_api//envoy/api/v2/route:pkg",
15+
"@envoy_api//envoy/type/matcher:pkg",
16+
"@envoy_api//envoy/type:pkg",
17+
],
18+
visibility = ["//visibility:public"],)

api/envoy/config/filter/http/transformation/v2/transformation_filter.proto

+27
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,33 @@ option java_outer_classname = "TransformationFilterProto";
77
option java_multiple_files = true;
88
option go_package = "transformation";
99

10+
import "validate/validate.proto";
11+
12+
import "envoy/api/v2/route/route.proto";
13+
14+
message FilterTransformations {
15+
// Specifies transformations based on the route matches. The first matched transformation will be
16+
// applied. If there are overlapped match conditions, please put the most specific match first.
17+
repeated TransformationRule transformations = 1;
18+
}
19+
20+
message TransformationRule {
21+
// The route matching parameter. Only when the match is satisfied, the "requires" field will
22+
// apply.
23+
//
24+
// For example: following match will match all requests.
25+
//
26+
// .. code-block:: yaml
27+
//
28+
// match:
29+
// prefix: /
30+
//
31+
api.v2.route.RouteMatch match = 1 [(validate.rules).message = {required: true}];
32+
// transformation to perform
33+
RouteTransformations route_transformations = 2;
34+
}
35+
36+
1037
message RouteTransformations {
1138
Transformation request_transformation = 1;
1239
// clear the route cache if the request transformation was applied
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
changelog:
2+
- type: NEW_FEATURE
3+
issueLink: https://github.com/solo-io/envoy-gloo/issues/34
4+
description: Add envoy header matching into transformation filter.

source/common/matcher/BUILD

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
licenses(["notice"]) # Apache 2
2+
3+
load(
4+
"@envoy//bazel:envoy_build_system.bzl",
5+
"envoy_cc_binary",
6+
"envoy_cc_library",
7+
"envoy_cc_test",
8+
"envoy_package",
9+
)
10+
11+
envoy_package()
12+
13+
envoy_cc_library(
14+
name = "matchers_lib",
15+
srcs = ["matcher.cc"],
16+
hdrs = ["matcher.h"],
17+
repository = "@envoy",
18+
external_deps = ["abseil_optional"],
19+
deps = [
20+
"@envoy//source/common/router:config_lib",
21+
"@envoy//source/common/http:header_utility_lib",
22+
"@envoy_api//envoy/api/v2/route:pkg_cc_proto",
23+
],
24+
)
25+

source/common/matcher/matcher.cc

+160
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
#include "common/matcher/matcher.h"
2+
3+
#include "common/common/logger.h"
4+
#include "common/common/regex.h"
5+
#include "common/router/config_impl.h"
6+
7+
#include "absl/strings/match.h"
8+
9+
using ::envoy::api::v2::route::RouteMatch;
10+
using Envoy::Router::ConfigUtility;
11+
12+
namespace Envoy {
13+
namespace Matcher {
14+
namespace {
15+
16+
/**
17+
* Perform a match against any HTTP header or pseudo-header.
18+
*/
19+
class BaseMatcherImpl : public Matcher, public Logger::Loggable<Logger::Id::filter> {
20+
public:
21+
BaseMatcherImpl(const ::envoy::api::v2::route::RouteMatch& match)
22+
: case_sensitive_(PROTOBUF_GET_WRAPPED_OR_DEFAULT(match, case_sensitive, true)),
23+
config_headers_(Http::HeaderUtility::buildHeaderDataVector(match.headers())) {
24+
for (const auto& query_parameter : match.query_parameters()) {
25+
config_query_parameters_.push_back(
26+
std::make_unique<Router::ConfigUtility::QueryParameterMatcher>(query_parameter));
27+
}
28+
}
29+
30+
// Check match for HeaderMatcher and QueryParameterMatcher
31+
bool matchRoute(const Http::HeaderMap& headers) const {
32+
bool matches = true;
33+
// TODO(potatop): matching on RouteMatch runtime is not implemented.
34+
35+
matches &= Http::HeaderUtility::matchHeaders(headers, config_headers_);
36+
if (!config_query_parameters_.empty()) {
37+
Http::Utility::QueryParams query_parameters =
38+
Http::Utility::parseQueryString(headers.Path()->value().getStringView());
39+
matches &= ConfigUtility::matchQueryParams(query_parameters, config_query_parameters_);
40+
}
41+
return matches;
42+
}
43+
44+
protected:
45+
const bool case_sensitive_;
46+
47+
private:
48+
std::vector<Http::HeaderUtility::HeaderDataPtr> config_headers_;
49+
std::vector<Router::ConfigUtility::QueryParameterMatcherPtr> config_query_parameters_;
50+
};
51+
52+
/**
53+
* Perform a match against any path with prefix rule.
54+
*/
55+
class PrefixMatcherImpl : public BaseMatcherImpl {
56+
public:
57+
PrefixMatcherImpl(const ::envoy::api::v2::route::RouteMatch& match)
58+
: BaseMatcherImpl(match), prefix_(match.prefix()) {}
59+
60+
bool matches(const Http::HeaderMap& headers) const override {
61+
if (BaseMatcherImpl::matchRoute(headers) &&
62+
(case_sensitive_
63+
? absl::StartsWith(headers.Path()->value().getStringView(), prefix_)
64+
: absl::StartsWithIgnoreCase(headers.Path()->value().getStringView(), prefix_))) {
65+
ENVOY_LOG(debug, "Prefix requirement '{}' matched.", prefix_);
66+
return true;
67+
}
68+
return false;
69+
}
70+
71+
private:
72+
// prefix string
73+
const std::string prefix_;
74+
};
75+
76+
/**
77+
* Perform a match against any path with a specific path rule.
78+
*/
79+
class PathMatcherImpl : public BaseMatcherImpl {
80+
public:
81+
PathMatcherImpl(const ::envoy::api::v2::route::RouteMatch& match)
82+
: BaseMatcherImpl(match), path_(match.path()) {}
83+
84+
bool matches(const Http::HeaderMap& headers) const override {
85+
if (BaseMatcherImpl::matchRoute(headers)) {
86+
const Http::HeaderString& path = headers.Path()->value();
87+
const size_t compare_length =
88+
path.getStringView().length() - Http::Utility::findQueryStringStart(path).length();
89+
auto real_path = path.getStringView().substr(0, compare_length);
90+
bool match = case_sensitive_ ? real_path == path_ : StringUtil::caseCompare(real_path, path_);
91+
if (match) {
92+
ENVOY_LOG(debug, "Path requirement '{}' matched.", path_);
93+
return true;
94+
}
95+
}
96+
return false;
97+
}
98+
99+
private:
100+
// path string.
101+
const std::string path_;
102+
};
103+
104+
/**
105+
* Perform a match against any path with a regex rule.
106+
* TODO(mattklein123): This code needs dedup with RegexRouteEntryImpl.
107+
*/
108+
class RegexMatcherImpl : public BaseMatcherImpl {
109+
public:
110+
RegexMatcherImpl(const ::envoy::api::v2::route::RouteMatch& match) : BaseMatcherImpl(match) {
111+
if (match.path_specifier_case() == envoy::api::v2::route::RouteMatch::kRegex) {
112+
regex_ = Regex::Utility::parseStdRegexAsCompiledMatcher(match.regex());
113+
regex_str_ = match.regex();
114+
} else {
115+
ASSERT(match.path_specifier_case() == envoy::api::v2::route::RouteMatch::kSafeRegex);
116+
regex_ = Regex::Utility::parseRegex(match.safe_regex());
117+
regex_str_ = match.safe_regex().regex();
118+
}
119+
}
120+
121+
bool matches(const Http::HeaderMap& headers) const override {
122+
if (BaseMatcherImpl::matchRoute(headers)) {
123+
const Http::HeaderString& path = headers.Path()->value();
124+
const absl::string_view query_string = Http::Utility::findQueryStringStart(path);
125+
absl::string_view path_view = path.getStringView();
126+
path_view.remove_suffix(query_string.length());
127+
if (regex_->match(path_view)) {
128+
ENVOY_LOG(debug, "Regex requirement '{}' matched.", regex_str_);
129+
return true;
130+
}
131+
}
132+
return false;
133+
}
134+
135+
private:
136+
Regex::CompiledMatcherPtr regex_;
137+
// raw regex string, for logging.
138+
std::string regex_str_;
139+
};
140+
141+
} // namespace
142+
143+
MatcherConstPtr Matcher::create(const ::envoy::api::v2::route::RouteMatch& match) {
144+
switch (match.path_specifier_case()) {
145+
case RouteMatch::PathSpecifierCase::kPrefix:
146+
return std::make_shared<PrefixMatcherImpl>(match);
147+
case RouteMatch::PathSpecifierCase::kPath:
148+
return std::make_shared<PathMatcherImpl>(match);
149+
case RouteMatch::PathSpecifierCase::kRegex:
150+
case RouteMatch::PathSpecifierCase::kSafeRegex:
151+
return std::make_shared<RegexMatcherImpl>(match);
152+
// path specifier is required.
153+
case RouteMatch::PathSpecifierCase::PATH_SPECIFIER_NOT_SET:
154+
default:
155+
NOT_REACHED_GCOVR_EXCL_LINE;
156+
}
157+
}
158+
159+
} // namespace Matcher
160+
} // namespace Envoy

source/common/matcher/matcher.h

+39
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#pragma once
2+
3+
#include "envoy/api/v2/route/route.pb.h"
4+
#include "envoy/http/header_map.h"
5+
6+
namespace Envoy {
7+
namespace Matcher {
8+
9+
class Matcher;
10+
using MatcherConstPtr = std::shared_ptr<const Matcher>;
11+
12+
/**
13+
* Supports matching a HTTP requests with JWT requirements.
14+
*/
15+
class Matcher {
16+
public:
17+
virtual ~Matcher() = default;
18+
19+
/**
20+
* Returns if a HTTP request matches with the rules of the matcher.
21+
*
22+
* @param headers the request headers used to match against. An empty map should be used if
23+
* there are none headers available.
24+
* @return true if request is a match, false otherwise.
25+
*/
26+
virtual bool matches(const Http::HeaderMap& headers) const PURE;
27+
28+
/**
29+
* Factory method to create a shared instance of a matcher based on the rule defined.
30+
*
31+
* @param rule the proto rule match message.
32+
* @return the matcher instance.
33+
*/
34+
static MatcherConstPtr
35+
create(const ::envoy::api::v2::route::RouteMatch& match);
36+
};
37+
38+
} // namespace Extensions
39+
} // namespace Envoy

source/extensions/filters/http/transformation/BUILD

+3
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,12 @@ envoy_cc_library(
9898
],
9999
repository = "@envoy",
100100
deps = [
101+
"//source/common/matcher:matchers_lib",
101102
"@envoy//include/envoy/buffer:buffer_interface",
102103
"@envoy//include/envoy/http:header_map_interface",
103104
"@envoy//include/envoy/http:filter_interface",
104105
"@envoy//include/envoy/router:router_interface",
106+
"@envoy//source/common/http:header_utility_lib",
105107
],
106108
)
107109

@@ -118,3 +120,4 @@ envoy_cc_library(
118120
"@envoy//source/extensions/filters/http/common:factory_base_lib",
119121
],
120122
)
123+

0 commit comments

Comments
 (0)