router: add filter_state internal redirect predicate#45904
Conversation
Adds the envoy.internal_redirect_predicates.filter_state predicate, which decides whether to follow an internal redirect by comparing a filter state string object (set earlier in the request by another filter) against a configured value: allow_value follows only on a match, deny_value follows unless it matches, and allow_if_absent controls behavior when the object is missing (default: do not follow). No change for existing configs; the predicate has effect only when added to an InternalRedirectPolicy. Uses a raw string with a required oneof allow_value / deny_value rather than a StringMatcher, because the predicate factory does not receive a CommonFactoryContext (needed by StringMatcher for regex/custom). This stays additively upgradeable to a StringMatcher option later without an API break. Signed-off-by: Juan Manuel Olle <jolle@salesforce.com>
Signed-off-by: Juan Manuel Olle <jolle@salesforce.com>
|
CC @envoyproxy/api-shepherds: Your approval is needed for changes made to |
adisuissa
left a comment
There was a problem hiding this comment.
Thanks.
Left a high-level question about the design.
| // whether an otherwise-enabled internal redirect is actually taken, without | ||
| // changing route matching. | ||
| // | ||
| // The predicate reads the filter-state object named ``filter_state_key`` (a |
There was a problem hiding this comment.
From a high-level perspective:
it seems that in the future this will have a matcher that is dependent on the expected type. The most basic one will be a string matcher, but the API should be designed in a way that allows other types of matchers.
There was a problem hiding this comment.
I thought that using oneof will allow in the future to add others like string matcher with backward compatibility
oneof policy {
string allow_value = 2; // existing - exact string match
string deny_value = 3; // existing - exact string match
StringMatcher allow_string_matcher = 5; // NEW - regex, prefix, suffix, etc.
StringMatcher deny_string_matcher = 6; // NEW - regex, prefix, suffix, etc.
}
if you have something better in mind let me know I can change it
There was a problem hiding this comment.
IIUC, @adisuissa means that the type of data store in the filter state might be more than a string.
From an API point of view, the policy and the filter_state_key is coupled by the type of the filter state's type.
What happens if the filter state pointed by filter_state_key stores something other than string?
An alternative might be:
string filter_state_key = 1;
enum MatchAction {
UNSPECIFIED = 0;
ALLOW = 1;
DENY = 2;
}
MatchAction match_action = 2;
oneof matcher {
string string_value = 1;
// other types of matcher for extension
}
With all the above said, isn't it simpler for this to just read a boolean from a filter state that dictates whether to follow the redirect? What's the use case that requires a string value to be compared against?
There was a problem hiding this comment.
I didn't see a boolean could work and perhaps it is cleaner
Do you have something like this in mind?
message FilterStateConfig {
// Name of the filter-state boolean object. If true, follows the redirect.
string redirect_enabled_key = 1 [(validate.rules).string = {min_len: 1}];
// Whether to follow the redirect when the filter-state object is absent.
// Defaults to false (do not redirect if the gate wasn't explicitly set).
bool redirect_if_absent = 2;
}
There was a problem hiding this comment.
I think this is a lot cleaner while being really flexible - whichever filter producing the filter state can decide what to do.
There was a problem hiding this comment.
the code is cleaner too (and just a couple of lines) I will do some exploratory test and update the PR, thanks
|
Assigning internal_redirect code-owners: |
penguingao
left a comment
There was a problem hiding this comment.
Thanks for the contribution. This looks great. Just need to figure out the API discussion.
| // whether an otherwise-enabled internal redirect is actually taken, without | ||
| // changing route matching. | ||
| // | ||
| // The predicate reads the filter-state object named ``filter_state_key`` (a |
There was a problem hiding this comment.
IIUC, @adisuissa means that the type of data store in the filter state might be more than a string.
From an API point of view, the policy and the filter_state_key is coupled by the type of the filter state's type.
What happens if the filter state pointed by filter_state_key stores something other than string?
An alternative might be:
string filter_state_key = 1;
enum MatchAction {
UNSPECIFIED = 0;
ALLOW = 1;
DENY = 2;
}
MatchAction match_action = 2;
oneof matcher {
string string_value = 1;
// other types of matcher for extension
}
With all the above said, isn't it simpler for this to just read a boolean from a filter state that dictates whether to follow the redirect? What's the use case that requires a string value to be compared against?
| A fourth predicate :ref:`filter_state | ||
| <envoy_v3_api_msg_extensions.internal_redirect.filter_state.v3.FilterStateConfig>` | ||
| can be used to gate the redirect on a filter state value set earlier in the request by another | ||
| filter, allowing a per-request decision about whether an internal redirect is followed. |
There was a problem hiding this comment.
looks like we just need it to be per-request, in which case, it's really just a boolean to be delivered.
| const envoy::extensions::internal_redirect::filter_state::v3::FilterStateConfig&>( | ||
| config, ProtobufMessage::getStrictValidationVisitor()); | ||
|
|
||
| using ProtoConfig = envoy::extensions::internal_redirect::filter_state::v3::FilterStateConfig; |
There was a problem hiding this comment.
If you move this above line 19, you can use it with the call to downcastAndValidate as well.
| EXPECT_EQ(predicate->name(), "envoy.internal_redirect_predicates.filter_state"); | ||
| } | ||
|
|
||
| // allow_value: follow only when the gate equals the configured value. |
There was a problem hiding this comment.
the tests here are not "config_test" - we might want to just rename this file to reflect the nature of this test file.
Signed-off-by: Juan Manuel Olle <jolle@salesforce.com>
16a6b09 to
862c0d3
Compare
Signed-off-by: Juan Manuel Olle <jolle@salesforce.com>
| format_string: | ||
| text_format_source: | ||
| inline_string: "true" | ||
|
|
There was a problem hiding this comment.
I don't know if there was a reason to not have this generic bool, I added it to be able to write bool directly in filter state, if this is not correct let me know and I will remove and store "true" as string in the filter state
|
@penguingao @adisuissa If you have some time to take a look, I will appreciate |
penguingao
left a comment
There was a problem hiding this comment.
Thanks! This looks great to me modulo a few nits.
| @@ -0,0 +1,8 @@ | |||
| Added :ref:`filter_state | |||
| <envoy_v3_api_msg_extensions.internal_redirect.filter_state.v3.FilterStateConfig>`, | |||
| a new internal redirect predicate that gates redirect decisions on a boolean filter-state | |||
There was a problem hiding this comment.
nit: s/a new internal redirect/an internal redirect
this will not be new in some future point.
| object set earlier in the request lifecycle (for example by a Lua filter, ext_proc, | ||
| ``set_filter_state``, or a dynamic module). The predicate follows the redirect when the | ||
| boolean value is true, enabling per-request redirect control without changing route | ||
| matching. Also added the generic ``envoy.bool`` filter state factory for creating |
There was a problem hiding this comment.
nit: "Also added ..." here belongs to release note instead of the static documentation.
| std::unique_ptr<StreamInfo::FilterState::Object> | ||
| createFromBytes(absl::string_view data) const override { | ||
| bool value = false; | ||
| (void)absl::SimpleAtob(data, &value); |
There was a problem hiding this comment.
I'd prefer we throw if this is false.
it's better to reject bad config than taking an assumption.
| } | ||
|
|
||
| private: | ||
| using ProtoConfig = envoy::extensions::internal_redirect::filter_state::v3::FilterStateConfig; |
- changelog: use 'an' instead of 'a new', remove envoy.bool sentence - GenericBoolObjectFactory: throw on invalid input instead of defaulting to false - config.h: remove duplicate ProtoConfig typedef Signed-off-by: Juan Manuel Olle <jolle@salesforce.com>
- changelog: use 'an' instead of 'a new', remove envoy.bool sentence - GenericBoolObjectFactory: throw on invalid input instead of defaulting to false - config.h: move ProtoConfig typedef to private class scope to avoid duplication Signed-off-by: Juan Manuel Olle <jolle@salesforce.com>
|
This LGTM. @botengyao for API approval and merge, unless @adisuissa has more feedbacks. |
Commit Message:
router: add
filter_stateinternal redirect predicateAdds the
envoy.internal_redirect_predicates.filter_statepredicate, which decideswhether to follow an internal redirect based on a boolean filter state object. The
predicate reads a
BoolAccessorat the configuredfilter_state_keyand acceptsthe redirect if the value is true (or false if
invert: true).Also adds a generic
envoy.boolfilter state factory that accepts:true,t,yes,y,1for true;false,f,no,n,0for false (case-insensitive).Example configuration:
Setting from Lua:
Risk Level: Low — self-contained extension, inert unless explicitly configured.
Testing: Unit tests for predicate and factory. Integration tests with Docker validate
end-to-end behavior.
Docs Changes: Added
envoy.boolfactory towell_known_filter_state.rst.Release Notes: Added
changelogs/current/new_features/router__added-filter-state-internal-redirect-predicate.rst.[Generative AI usage disclosure: Generative AI (Claude) was used to assist with this change;
I have reviewed and understand all of the code.]