Skip to content
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

headers: Ability to rewrite headers received from upstream cluster #12938

Closed
wants to merge 24 commits into from
Closed

headers: Ability to rewrite headers received from upstream cluster #12938

wants to merge 24 commits into from

Conversation

shivanshu21
Copy link

@shivanshu21 shivanshu21 commented Sep 2, 2020

…response

Signed-off-by: Shivanshu Goswami shigoswami@ebay.com

Fixes: #12591
Commit Message:
Additional Description:
Risk Level: low
Testing: Added unit tests, performed manual testing
Docs Changes: Added
Release Notes:
[Optional Runtime guard:]
[Optional Fixes #Issue]
[Optional Deprecated:]

@repokitteh-read-only
Copy link

CC @envoyproxy/api-shepherds: Your approval is needed for changes made to api/envoy/.
CC @envoyproxy/api-watchers: FYI only for changes made to api/envoy/.

🐱

Caused by: #12938 was opened by shivanshu21.

see: more, trace.

// prefix: "/itm/"
// route:
// location_header_rewrite: "/n/error"
string location_header_rewrite = 36
Copy link
Member

Choose a reason for hiding this comment

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

Do you think there will ever be a case to rewrite via regex, e.g. using RegexMatchAndSubstitute, or does it seem that a literal location always works?

I wonder if we want a more general Rewriter message that can be both string and RegexMatchAndSubstitute @envoyproxy/api-shepherds

Copy link
Author

Choose a reason for hiding this comment

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

I was thinking about it too. Regex based rewrite will be more useful. Let me see how I can do that.

Copy link
Member

Choose a reason for hiding this comment

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

+1 to have a wrapper message that contains an internal oneof with different rewrite types.

Copy link

Choose a reason for hiding this comment

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

location HTTP header rewrites are exactly what I need as well! However, could this be more generalized to support any HTTP header rewrite with regex?

@shivanshu21
Copy link
Author

shivanshu21 commented Sep 3, 2020

@ccravens
I am okay with a generic response_headers_to_rewrite API which we can use in out Location rewrite usecase.
@alyssawilk @htuch Your thoughts?

We can have an API which is like this:

oneof header_rewrite_specifier {
    typename1 header_rewrite_string = 1;
    typename2 header_rewrite_regex = 2;
}

Typename1 and Typename2 consist of Header name and rewrite pattern which could be a string or regex

@shivanshu21
Copy link
Author

Just went through @ccravens open issue.

responseHeaderPolicy:
            set:
              - name: X-Service-Name
                value: s1
            remove:
              - X-Internal-Secret
            replace:
              - header: location
                regex: \http:\
                value: https:

This kind of API looks better. Will wait for suggestions. This takes care of add/remove as well as modify on response headers.
Wondering if we should introduce this at both Route as well as Virtualhost levels.

@htuch
Copy link
Member

htuch commented Sep 8, 2020

We already have API fields to add/remove response headers, so I would discourage introducing a redundant mechanism unless there is a plan to deprecate the existing mechanisms (and have your new scheme apply hierarchically similar to the existing one).

@ccravens
Copy link

ccravens commented Sep 8, 2020

@htuch thanks for your response. I don't think we're requesting to deprecate functionality. I'm aware and using the add / remove functionality. I think we're requesting a feature that addresses a need/requirement that the add/remove functions don't provide. @shivanshu21 let me know if I'm mistaken in that assumption from your side, thanks

@shivanshu21
Copy link
Author

shivanshu21 commented Sep 8, 2020

@htuch @ccravens If we try to introduce a comprehensive responseHeaderPolicy like the following:

responseHeaderPolicy:
            set:
              - name: X-Service-Name
                value: s1
            remove:
              - X-Internal-Secret
            replace:
              - header: location
                regex: \http:\
                value: https:

We would be adding new options to add/remove header too as shown above in set/remove sections, and then it would make sense to change existing APIs for add/remove. But looks like that is not what we want to do right now.

Going by what @htuch said, I think it should suffice to have a response_headers_to_rewrite field which can take the name of the header, a value to match and the substitution value. Match can be based on regex as well as a blanket replacement string.

The core idea is that this should be generic for all headers, not just location.

@shivanshu21
Copy link
Author

/wait

@shivanshu21
Copy link
Author

/wait

Will have to redo the code changes. @htuch @ccravens please take a look at the Proto files for the API changes in the latest commit and let me know if the API is satisfactory.

// In the below example there are two response header rewrites mentioned: one for location header
// and one for x-random-token header.
//
// The location header's value will be matched with the given match pattern as a prefix to the header's
Copy link
Member

Choose a reason for hiding this comment

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

You probably want to use envoy.type.v3.StringMatcher for this, rather than hardcode prefix match semantics.

// If no match string is given, the value is replaced entirely with the given substitution. The
// response status header is also matched before making a substitution. If the response status
// returned from the upstream cluster matches with one of the response statuses in the list, the
// substitution can proceed after matching the match section. If this list is empty, response status
Copy link
Member

Choose a reason for hiding this comment

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

I think the response status is really part of the matcher. Also, this only applies to the literal and not when we are doing regex substitute. I think you might want to move this up a level to a header_rewriter message.

// header_rewrite_literal:
// match: /itm/not-found/
// substitution: /n/error
// response_status:
Copy link
Member

Choose a reason for hiding this comment

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

Status is just one header you could potentially also match one. One thing you could consider doing here for matching is to look at what ResponseMapper in HCM does, where it uses AccessLogFilter as a general purpose matcher for responses prior to applying an action.

Copy link
Author

Choose a reason for hiding this comment

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

I looked at response mapper in HCM. It has a repeated field headers_to_add. They are trying to add headers, so the default assumption is that every header must be added.

For me the case is different. I am trying to match the headers.

repeated config.core.v3.HeaderValue upstream_headers_matcher = 3
      [(validate.rules).repeated = {max_items: 1000}];

Suppose I want to match:

  1. Response status should be either 301 OR 302 OR 307

  2. Response status is 200 AND content-type is text/html

For case 1, I would want that if ANY header KV pair matches, match the condition and rewrite the requested header.
For case 2, I would want that if ALL header KV pairs match, only then match the condition, and rewrite the requested header.

For this reason, I have introduced a list of acceptable response codes ANY of which could match.
I have also added a list of headers ALL of which must match.
Header rewrite will happen when both the subconditions match.

Copy link
Member

Choose a reason for hiding this comment

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

My question was more around whether you could use AccessLogFilter, which is more general purpose than what you currently have.

@shivanshu21
Copy link
Author

/wait

// part is substituted. In case of present match the whole value of the header is overwritten.
//
// The x-random-token header's value will be matched using the regex pattern specified in the pattern
// section, and the matched part will be replaced by the value provided in the substitution section.
Copy link
Member

Choose a reason for hiding this comment

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

Can you comment individual fields rather than a large detached block comment? Please take a look at the generated docs to see how they render, e.g. https://380489-65214191-gh.circle-artifacts.com/0/generated/docs/api-v3/config/route/v3/route_components.proto.html#envoy-v3-api-msg-config-route-v3-routeaction-headerrewriter

// header_rewrite_literal:
// match: /itm/not-found/
// substitution: /n/error
// response_status:
Copy link
Member

Choose a reason for hiding this comment

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

My question was more around whether you could use AccessLogFilter, which is more general purpose than what you currently have.

Copy link
Member

@htuch htuch left a comment

Choose a reason for hiding this comment

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

Looks good, mostly docs and would like to get agreement on the matcher form.

@shivanshu21
Copy link
Author

ERROR: /build/tmp/_bazel_root/b570b5ccd0454dc9af9f65ab1833764d/external/envoy_api_canonical/envoy/config/route/v3/BUILD:7:18: in proto_library rule @envoy_api_canonical//envoy/config/route/v3:pkg: cycle in dependency graph:
    //tools/protoxform:protoprint
    //tools/type_whisperer:api_type_db.pb_text
    //tools/type_whisperer:api_type_db
    //tools/type_whisperer:api_type_db_target
    @envoy_api_canonical//versioning:active_protos
    @envoy_api_canonical//envoy/config/common/matcher/v3:pkg
.-> @envoy_api_canonical//envoy/config/route/v3:pkg.     <<<<<<<<<<<<<
|   @envoy_api_canonical//envoy/config/accesslog/v3:pkg <<<<<<<<<<<<<
`-- @envoy_api_canonical//envoy/config/route/v3:pkg
This cycle occurred because of a configuration option

@htuch
AccessLogFilter proto refers to route components proto. So there is a cyclic dependency if we refer to AccessLogFilter proto in route components.

I like the idea of and_filter and or_filter. I am gravitating towards duplication as AccessLogFilter anyway does not have a reason to be in Route components. Thinking of introducing a message just like access log filter in route components.

@shivanshu21
Copy link
Author

/wait

HeaderMatcherFilter request_match_criteria = 3 [(validate.rules).message = {required: true}];
}

message HeaderMatcherFilter {
Copy link
Member

Choose a reason for hiding this comment

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

Thanks. How strongly do @envoyproxy/api-shepherds feel about trying to clean this up further in this PR? I think the ideal would be to migrate both the matching going on in AccessLogfilter and HeaderMatcher to envoy.type.matcher.v3.

Copy link
Member

Choose a reason for hiding this comment

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

Sorry @htuch what are you proposing? cc @snowp @yangminzhu for other efforts in flight to merge matching behavior. I agree at a high level that it pains me to see yet another and/or, etc. in the API here.

Copy link
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thanks for working on this and taking the time to get this right.

reserved 12, 18, 19, 16, 22, 21, 10;

reserved "request_mirror_policy";

map<string, HeaderRewriter> response_headers_to_rewrite = 36;
Copy link
Member

Choose a reason for hiding this comment

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

Please add docs for this field. Also, we haven't typically used a map for things like this. IMO it would be more future proof to have a list of messages, even if internally it is translated to a map? @envoyproxy/api-shepherds?

HeaderMatcherFilter request_match_criteria = 3 [(validate.rules).message = {required: true}];
}

message HeaderMatcherFilter {
Copy link
Member

Choose a reason for hiding this comment

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

Sorry @htuch what are you proposing? cc @snowp @yangminzhu for other efforts in flight to merge matching behavior. I agree at a high level that it pains me to see yet another and/or, etc. in the API here.

@mattklein123 mattklein123 self-assigned this Sep 14, 2020
Shivanshu Goswami added 14 commits October 20, 2020 00:47
* Add UpstreamHeaderRewriter and HeaderRewriteLiteral classes
* Call rewriteUpstreamResponseHeaders() from finalizeResponseHeaders()
* Only allow exact and prefix match on header value
* Fix substitution string logic
* Fix format
* Adjusting messages and comments

Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
…rbage

Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
@htuch
Copy link
Member

htuch commented Oct 20, 2020

@shivanshu21 I'll give this a review now, but please try avoid force pushes, as they make it hard to track deltas during review.

HeaderRewriteLiteral value_rewrite_literal = 1;
}

type.matcher.v3.MatchPredicate response_match_criteria = 2;
Copy link
Member

Choose a reason for hiding this comment

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

Comments for all fields, thanks (there are some others in this PR that are not commented.

Copy link
Author

Choose a reason for hiding this comment

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

Done

// [#next-free-field: 37]
message HeaderRewriteLiteral {
// String match can use prefix match and present match. In case of prefix match, the matched prefix part of
// the header's value is substituted. In case of present match the whole value of the header is overwritten.
Copy link
Member

Choose a reason for hiding this comment

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

What are the semantics for the other string match types? Does capture group semantics apply?

I wonder if we want a StringMatchAndSubstitute that is analogous to RegexMatchAndSubstitute.

Copy link
Author

Choose a reason for hiding this comment

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

Capture group semantics do not apply but this can be a future enhancement in the message HeaderMatchAndRewrite which has a oneof type with an option for this (HeaderRewriteLiteral) message.

Added explanation for other StringMatcher types.

message UpstreamHeaderRewriter {
string header_name = 1;

HeaderMatchAndRewrite match_and_rewrite = 2;
Copy link
Member

Choose a reason for hiding this comment

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

Comments.

Copy link
Author

Choose a reason for hiding this comment

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

Done

// In the example below, we rewrite the location header received from the upstream cluster
// if the location header is present.
//
// If a header is specified in response_headers_to_rewrite section but is not returned from the
Copy link
Member

Choose a reason for hiding this comment

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

Nit: in the *response_headers_to_write* field.

// "exact_match": "POST"
// }
// }
// [#next-major-version: This message deprecates config.route.v3.HeaderMatcher]
Copy link
Member

Choose a reason for hiding this comment

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

Please add an explicit comment on this as well for user-facing docs, since users will want to know which one to use.

Copy link
Author

Choose a reason for hiding this comment

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

Done

// [#protodoc-title: Logical Matcher API]

// Match configuration. This is a recursive structure which allows complex nested match
// configurations to be built using various logical operators.
Copy link
Member

Choose a reason for hiding this comment

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

Same comment here on user facing docs.

Copy link
Author

Choose a reason for hiding this comment

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

Done

Shivanshu Goswami added 2 commits October 22, 2020 00:21
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Copy link
Author

@shivanshu21 shivanshu21 left a comment

Choose a reason for hiding this comment

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

config_impl_test passes in local

envoy/config/listener/v3/udp_gso_batch_writer_config.proto:5:1: warning: Import google/protobuf/duration.proto but not used.
envoy/config/listener/v3/udp_gso_batch_writer_config.proto:6:1: warning: Import google/protobuf/wrappers.proto but not used.
INFO: From Generating Descriptor Set proto_library @envoy_api//envoy/admin/v3:pkg:
envoy/admin/v3/init_dump.proto:6:1: warning: Import udpa/annotations/versioning.proto but not used.
Target //test/common/router:config_impl_test up-to-date:
  bazel-bin/test/common/router/config_impl_test
INFO: Elapsed time: 118.371s, Critical Path: 113.56s
INFO: 14 processes: 14 processwrapper-sandbox.
INFO: Build completed successfully, 15 total actions
//test/common/router:config_impl_test                                    PASSED in 11.4s

Executed 1 out of 1 test: 1 test passes.
INFO: Build completed successfully, 15 total actions

// [#next-free-field: 37]
message HeaderRewriteLiteral {
// String match can use prefix match and present match. In case of prefix match, the matched prefix part of
// the header's value is substituted. In case of present match the whole value of the header is overwritten.
Copy link
Author

Choose a reason for hiding this comment

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

Capture group semantics do not apply but this can be a future enhancement in the message HeaderMatchAndRewrite which has a oneof type with an option for this (HeaderRewriteLiteral) message.

Added explanation for other StringMatcher types.

Shivanshu Goswami added 2 commits October 22, 2020 01:36
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Copy link
Member

@lizan lizan left a comment

Choose a reason for hiding this comment

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

In general this seems on a right direction, though it would be nice to wait a bit to land the new matcher API (at least in proto level), so we would have less tech debt to clean up.

@@ -39,6 +39,7 @@ message TapConfig {
// Exactly one of :ref:`match <envoy_api_field_config.tap.v3.TapConfig.match>` and
// :ref:`match_config <envoy_api_field_config.tap.v3.TapConfig.match_config>` must be set. If both
// are set, the :ref:`match <envoy_api_field_config.tap.v3.TapConfig.match>` will be used.
// [#next-major-version: common.matcher.v3.MatchPredicate should be refactored to use type.matcher.v3.MatchPredicate.]
Copy link
Member

Choose a reason for hiding this comment

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

This doc change for tap isn't really related? And we don't have type.matcher.v3.MatchPredicate (same for elsewhere), and that's not part of the new matcher API either.

Copy link
Author

Choose a reason for hiding this comment

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

type.matcher.v3.MatchPredicate This is being introduced in this PR. We spoke to @snowp and the Unified matcher is going to take time.

I will handle the tech debt in the next, separate PR while HeaderMatcher references are moved to StringMatcher based type.Matcher.v3.HeaderMatcher (which is also introduced in this PR).

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I know that is what we said, but we are actively working on the new matching protos now. I think it's worth intersecting these efforts. Let's circle back next week and see where we are at.

Copy link
Author

Choose a reason for hiding this comment

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

Okay

Shivanshu Goswami added 2 commits October 22, 2020 16:24
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Signed-off-by: Shivanshu Goswami <shigoswami@ebay.com>
Base automatically changed from master to main January 15, 2021 23:01
@alyssawilk alyssawilk closed this Feb 22, 2021
@databus23
Copy link

Just curios why was the closed? Is it still planned to support response header rewriting eventually?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Need 3XX Location URL rewrite feature
9 participants