-
Notifications
You must be signed in to change notification settings - Fork 5.4k
ext_authz: support modifying and removing query string parameters when using a gRPC authorization server #18009
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 33 commits
12230f0
a934913
cad93ec
426a467
25b2eeb
5c9c01e
06a6338
b9fc09e
251655e
7686b3c
9edf4c0
591f75a
b2b3dec
e67a46e
43aae44
95872b4
68185f1
2d63f10
08ba422
617f90e
32786f4
46004e6
e740588
427be58
181dcc1
4474842
ddcd3aa
a2674e3
020918e
19383d6
7b52929
8837536
5fe5ee3
a9d7f5a
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 |
|---|---|---|
|
|
@@ -248,6 +248,20 @@ absl::string_view findQueryStringStart(const HeaderString& path); | |
| */ | ||
| std::string stripQueryString(const HeaderString& path); | ||
|
|
||
| /** | ||
| * Replace the query string portion of a given path with a new one. | ||
| * | ||
| * e.g. replaceQueryString("/foo?key=1", {key:2}) -> "/foo?key=2" | ||
|
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. Since there is no standard on passing multiple values (for the same key, e.g. ?a=1&a=2, it is common that web framework perceives this as a "collection", for example: https://docs.microsoft.com/en-us/previous-versions/iis/6.0-sdk/ms524784(v=vs.90)?redirectedfrom=MSDN#remarks) in the query string, I think we need to be explicit here to do "replace all" or just the first occurrence. Or as wild as: https://github.com/ljharb/qs#parsing-arrays
Contributor
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. The example in the comment might be too simple and therefore misleading, but this utility function actually replaces the entire query string with the serialized representation of the given QueryParams. It doesn't attempt to parse the existing query string and do replacements on a key-by-key basis. |
||
| * replaceQueryString("/bar", {hello:there}) -> "/bar?hello=there" | ||
| * | ||
| * @param path the original path that may or may not contain an existing query string | ||
| * @param params the new params whose string representation should be formatted onto | ||
| * the `path` above | ||
| * @return std::string the new path whose query string has been replaced by `params` and whose path | ||
| * portion from `path` remains unchanged. | ||
| */ | ||
| std::string replaceQueryString(const HeaderString& path, const QueryParams& params); | ||
|
|
||
| /** | ||
| * Parse a particular value out of a cookie | ||
| * @param headers supplies the headers to get the cookie from. | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -222,12 +222,13 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { | |||||||||
|
|
||||||||||
| switch (response->status) { | ||||||||||
| case CheckStatus::OK: { | ||||||||||
| // Any changes to request headers can affect how the request is going to be | ||||||||||
| // Any changes to request headers or query parameters can affect how the request is going to be | ||||||||||
|
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 for this. I totally forgot that we have a route matcher to match query params. envoy/api/envoy/config/route/v3/route_components.proto Lines 1995 to 1998 in 7d3057f
|
||||||||||
| // routed. If we are changing the headers we also need to clear the route | ||||||||||
| // cache. | ||||||||||
| if (config_->clearRouteCache() && | ||||||||||
| (!response->headers_to_set.empty() || !response->headers_to_append.empty() || | ||||||||||
| !response->headers_to_remove.empty())) { | ||||||||||
| !response->headers_to_remove.empty() || !response->query_parameters_to_set.empty() || | ||||||||||
| !response->query_parameters_to_remove.empty())) { | ||||||||||
| ENVOY_STREAM_LOG(debug, "ext_authz is clearing route cache", *decoder_callbacks_); | ||||||||||
| decoder_callbacks_->clearRouteCache(); | ||||||||||
| } | ||||||||||
|
|
@@ -286,6 +287,42 @@ void Filter::onComplete(Filters::Common::ExtAuthz::ResponsePtr&& response) { | |||||||||
| response_headers_to_set_ = std::move(response->response_headers_to_set); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| absl::optional<Http::Utility::QueryParams> modified_query_parameters; | ||||||||||
| if (!response->query_parameters_to_set.empty()) { | ||||||||||
| modified_query_parameters = | ||||||||||
| Http::Utility::parseQueryString(request_headers_->Path()->value().getStringView()); | ||||||||||
| ENVOY_STREAM_LOG( | ||||||||||
| trace, "ext_authz filter set query parameter(s) on the request:", *decoder_callbacks_); | ||||||||||
| for (const auto& [key, value] : response->query_parameters_to_set) { | ||||||||||
| ENVOY_STREAM_LOG(trace, "'{}={}'", *decoder_callbacks_, key, value); | ||||||||||
| (*modified_query_parameters)[key] = value; | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (!response->query_parameters_to_remove.empty()) { | ||||||||||
| if (!modified_query_parameters) { | ||||||||||
| modified_query_parameters = | ||||||||||
| Http::Utility::parseQueryString(request_headers_->Path()->value().getStringView()); | ||||||||||
| } | ||||||||||
| ENVOY_STREAM_LOG(trace, "ext_authz filter removed query parameter(s) from the request:", | ||||||||||
| *decoder_callbacks_); | ||||||||||
| for (const auto& key : response->query_parameters_to_remove) { | ||||||||||
| ENVOY_STREAM_LOG(trace, "'{}'", *decoder_callbacks_, key); | ||||||||||
| (*modified_query_parameters).erase(key); | ||||||||||
| } | ||||||||||
| } | ||||||||||
|
|
||||||||||
| // We modified the query parameters in some way, so regenerate the `path` header and set it | ||||||||||
| // here. | ||||||||||
| if (modified_query_parameters) { | ||||||||||
| const auto new_path = Http::Utility::replaceQueryString(request_headers_->Path()->value(), | ||||||||||
| modified_query_parameters.value()); | ||||||||||
| ENVOY_STREAM_LOG( | ||||||||||
| trace, "ext_authz filter modified query parameter(s), using new path for request: {}", | ||||||||||
| *decoder_callbacks_, new_path); | ||||||||||
| request_headers_->setPath(new_path); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| if (cluster_) { | ||||||||||
| config_->incCounter(cluster_->statsScope(), config_->ext_authz_ok_); | ||||||||||
| } | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -91,6 +91,67 @@ TEST(HttpUtility, parseQueryString) { | |
| "bucket%7Crq_xx%7Crq_complete%7Crq_active%7Ccx_active%29%29%7C%28server.version%29")); | ||
| } | ||
|
|
||
| TEST(HttpUtility, stripQueryString) { | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/")), "/"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/?")), "/"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/?x=1")), "/"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo")), "/foo"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo?")), "/foo"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo?hello=there")), "/foo"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/?")), "/foo/"); | ||
|
Comment on lines
+97
to
+103
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. Nit: I know it's too pedantic, but I feel incompleteness without having these two lines added EXPECT_EQ(Utility::stripQueryString(HeaderString("/?x=1&y=2")), "/");
EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo?hello=there&good=bye")), "/foo");
Contributor
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. that's fair - will add them |
||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/?x=1")), "/foo/"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/bar")), "/foo/bar"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/bar?")), "/foo/bar"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/bar?a=b")), "/foo/bar"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/bar?a=b&b=c")), "/foo/bar"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/bar/")), "/foo/bar/"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/bar/?")), "/foo/bar/"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/bar/?x=1")), "/foo/bar/"); | ||
| EXPECT_EQ(Utility::stripQueryString(HeaderString("/foo/bar/?x=1&y=2")), "/foo/bar/"); | ||
| } | ||
|
|
||
| TEST(HttpUtility, replaceQueryString) { | ||
| // Replace with nothing | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/"), Utility::QueryParams()), "/"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/?"), Utility::QueryParams()), "/"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/?x=0"), Utility::QueryParams()), "/"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/a"), Utility::QueryParams()), "/a"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/a/"), Utility::QueryParams()), "/a/"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/a/?y=5"), Utility::QueryParams()), "/a/"); | ||
| // Replace with x=1 | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/"), Utility::QueryParams({{"x", "1"}})), | ||
| "/?x=1"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/?"), Utility::QueryParams({{"x", "1"}})), | ||
| "/?x=1"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/?x=0"), Utility::QueryParams({{"x", "1"}})), | ||
| "/?x=1"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/a?x=0"), Utility::QueryParams({{"x", "1"}})), | ||
| "/a?x=1"); | ||
| EXPECT_EQ( | ||
| Utility::replaceQueryString(HeaderString("/a/?x=0"), Utility::QueryParams({{"x", "1"}})), | ||
| "/a/?x=1"); | ||
| // More replacements | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/foo"), | ||
| Utility::QueryParams({{"x", "1"}, {"z", "3"}})), | ||
| "/foo?x=1&z=3"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/foo?z=2"), | ||
| Utility::QueryParams({{"x", "1"}, {"y", "5"}})), | ||
| "/foo?x=1&y=5"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/foo?y=9"), | ||
| Utility::QueryParams({{"x", "1"}, {"y", "5"}})), | ||
| "/foo?x=1&y=5"); | ||
| // More path components | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/foo/bar?"), | ||
| Utility::QueryParams({{"x", "1"}, {"y", "5"}})), | ||
| "/foo/bar?x=1&y=5"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/foo/bar?y=9&a=b"), | ||
| Utility::QueryParams({{"x", "1"}, {"y", "5"}})), | ||
| "/foo/bar?x=1&y=5"); | ||
| EXPECT_EQ(Utility::replaceQueryString(HeaderString("/foo/bar?y=11&z=7"), | ||
| Utility::QueryParams({{"a", "b"}, {"x", "1"}, {"y", "5"}})), | ||
| "/foo/bar?a=b&x=1&y=5"); | ||
| } | ||
|
|
||
| TEST(HttpUtility, getResponseStatus) { | ||
| EXPECT_THROW(Utility::getResponseStatus(TestResponseHeaderMapImpl{}), CodecClientException); | ||
| EXPECT_EQ(200U, Utility::getResponseStatus(TestResponseHeaderMapImpl{{":status", "200"}})); | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.