Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
320f403
new api for session sticky
wbpcode Sep 15, 2021
9342111
extension: new extensions for stateful session sticky
wbpcode Sep 22, 2021
a084c5d
Apply suggestions from code review
wbpcode Sep 23, 2021
ed30909
refactor: refactor stateful session with http filter
wbpcode Oct 4, 2021
8968f3c
Merge branch 'main' of https://github.com/envoyproxy/envoy into sessi…
wbpcode Oct 4, 2021
007e5c3
fix docs
wbpcode Oct 4, 2021
b5c28fd
add maintainers to the codeowner list
wbpcode Oct 21, 2021
fd2e635
update build
wbpcode Oct 21, 2021
b33b703
minor updates according to previous review comments
Oct 22, 2021
47aae23
Merge branch 'main' of https://github.com/envoyproxy/envoy into sessi…
Oct 22, 2021
0b6b70b
fix docs
Oct 22, 2021
22519a1
fix test case
Oct 23, 2021
4271423
fix clang tidy
Oct 24, 2021
85bffee
address comments
Nov 10, 2021
4671ea3
minor update of api docs
Nov 11, 2021
ea01e0a
add new integration tests and minor others updates
Nov 12, 2021
2baf32b
some minor updates
wbpcode Nov 18, 2021
2d47b26
Merge branch 'main' of https://github.com/envoyproxy/envoy into sessi…
wbpcode Nov 19, 2021
ff77ee8
Merge branch 'main' of https://github.com/envoyproxy/envoy into sessi…
wbpcode Dec 3, 2021
fb1583b
minor update
wbpcode Dec 7, 2021
4c353f3
Merge branch 'main' of https://github.com/envoyproxy/envoy into sessi…
wbpcode Dec 8, 2021
07ddaaa
fix format & add comments
wbpcode Dec 8, 2021
d955dfb
fix test
wbpcode Dec 9, 2021
7c92aad
add more detailed docs
wbpcode Dec 9, 2021
cac65a0
Merge branch 'main' of https://github.com/envoyproxy/envoy into sessi…
wbpcode Dec 19, 2021
8f1e514
remove health statuses from http filter
wbpcode Dec 20, 2021
087992e
add release note
wbpcode Dec 20, 2021
e735b2b
remove unused code
wbpcode Dec 21, 2021
c410b5f
create new common cookie API
wbpcode Dec 23, 2021
9e98452
minor docs updates
wbpcode Dec 29, 2021
23a800e
minor update to address reviewer's comments
wbpcode Jan 4, 2022
958b209
fix test
wbpcode Jan 4, 2022
5ab21ee
minor update
wbpcode Jan 5, 2022
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
4 changes: 4 additions & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp
/*/extensions/filters/common/local_ratelimit @mattklein123 @rgs1
# HTTP Kill Request
/*/extensions/filters/http/kill_request @qqustc @htuch
# HTTP Stateful Session
/*/extensions/filters/http/stateful_session @wbpcode @dio
# Rate limit expression descriptor
/*/extensions/rate_limit_descriptors/expr @kyessenov @lizan
# hash input matcher
Expand Down Expand Up @@ -195,6 +197,8 @@ extensions/filters/http/oauth2 @rgs1 @derekargueta @snowp
/*/extensions/matching/input_matchers/ip @aguinet @snowp
# Key Value store
/*/extensions/key_value @alyssawilk @ryantheoptimist
# Stateful session
/*/extensions/http/stateful_session/cookie @wbpcode @dio
# DNS Resolver
/*/extensions/network/dns_resolver/cares @junr03 @yanavlasov
/*/extensions/network/dns_resolver/apple @junr03 @yanavlasov
Expand Down
2 changes: 2 additions & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ proto_library(
"//envoy/extensions/filters/http/rbac/v3:pkg",
"//envoy/extensions/filters/http/router/v3:pkg",
"//envoy/extensions/filters/http/set_metadata/v3:pkg",
"//envoy/extensions/filters/http/stateful_session/v3:pkg",
"//envoy/extensions/filters/http/tap/v3:pkg",
"//envoy/extensions/filters/http/wasm/v3:pkg",
"//envoy/extensions/filters/listener/http_inspector/v3:pkg",
Expand Down Expand Up @@ -211,6 +212,7 @@ proto_library(
"//envoy/extensions/http/header_formatters/preserve_case/v3:pkg",
"//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg",
"//envoy/extensions/http/original_ip_detection/xff/v3:pkg",
"//envoy/extensions/http/stateful_session/cookie/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
Expand Down
12 changes: 12 additions & 0 deletions api/envoy/extensions/filters/http/stateful_session/v3/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.

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

licenses(["notice"]) # Apache 2

api_proto_package(
deps = [
"//envoy/config/core/v3:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
syntax = "proto3";

package envoy.extensions.filters.http.stateful_session.v3;

import "envoy/config/core/v3/extension.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.filters.http.stateful_session.v3";
option java_outer_classname = "StatefulSessionProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/stateful_session/v3;stateful_sessionv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Stateful session filter]
// Stateful session :ref:`configuration overview <config_http_filters_stateful_session>`.
// [#extension: envoy.filters.http.stateful_session]

message StatefulSession {
// Specific implementation of session state. This session state will be used to store and
// get address of the upstream host to which the session is assigned.
//
// [#extension-category: envoy.http.stateful_session]
config.core.v3.TypedExtensionConfig session_state = 1
[(validate.rules).message = {required: true}];
}
Comment thread
wbpcode marked this conversation as resolved.

message StatefulSessionPerRoute {
oneof override {
option (validate.required) = true;

// Disable the stateful session filter for this particular vhost or route. If disabled is
// specified in multiple per-filter-configs, the most specific one will be used.
bool disabled = 1 [(validate.rules).bool = {const: true}];

// Per-route stateful session configuration that can be served by RDS or static route table.
StatefulSession stateful_session = 2;
}
}
12 changes: 12 additions & 0 deletions api/envoy/extensions/http/stateful_session/cookie/v3/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# DO NOT EDIT. This file is generated by tools/proto_format/proto_sync.py.

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

licenses(["notice"]) # Apache 2

api_proto_package(
deps = [
"//envoy/type/http/v3:pkg",
"@com_github_cncf_udpa//udpa/annotations:pkg",
],
)
43 changes: 43 additions & 0 deletions api/envoy/extensions/http/stateful_session/cookie/v3/cookie.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
syntax = "proto3";

package envoy.extensions.http.stateful_session.cookie.v3;

import "envoy/type/http/v3/cookie.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.extensions.http.stateful_session.cookie.v3";
option java_outer_classname = "CookieProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/extensions/http/stateful_session/cookie/v3;cookiev3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Cookie based stateful session extension]

// This extension allows the session state to be tracked via cookies.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I was thinking a bit about multiple hops. For example, client -> external LB (envoy) -> internal LB (envoy) -> app.

I am assuming this would be broken if both envoy's configure this, right? Since both Envoys would have the cookie set but they would be configured the value to the host's of app or internal LB.

Is that right?

I don't think there is any code changes here, just something users should be aware of. If we have this we can set a different cookie at each hop I think

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point. Thanks for calling it out. I will add some new comments as note. 😄

//
// This extension first encodes the address of the upstream host selected by the load balancer
// into a `set-cookie` response header with the :ref:`cookie configuration
// <envoy_v3_api_field_extensions.http.stateful_session.cookie.v3.CookieBasedSessionState.cookie>`.
// when new requests are incoming, this extension will try to parse the specific upstream host
// address by the cookie name. If the address parsed from the cookie corresponds to a valid
// upstream host, this upstream host will be selected first. See :ref:`stateful session filter
// <envoy_v3_api_msg_extensions.filters.http.stateful_session.v3.StatefulSession>`.
//
// For example, if the cookie name is set to `sticky-host`, envoy will prefer `1.2.3.4:80`
// as the upstream host when the request contains the following header:
//
// .. code-block:: none
//
// cookie: sticky-host="MS4yLjMuNDo4MA=="
Comment thread
wbpcode marked this conversation as resolved.
//
// When processing the upstream response, if `1.2.3.4:80` is indeed the final choice the extension
// does nothing. If `1.2.3.4:80` is not the final choice, the new selected host will be used to
// update the cookie (via the `set-cookie` response header).
//
// [#extension: envoy.http.stateful_session.cookie]
message CookieBasedSessionState {
// The cookie configuration used to track session state.
type.http.v3.Cookie cookie = 1 [(validate.rules).message = {required: true}];
}
31 changes: 31 additions & 0 deletions api/envoy/type/http/v3/cookie.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
syntax = "proto3";

package envoy.type.http.v3;

import "google/protobuf/duration.proto";

import "udpa/annotations/status.proto";
import "validate/validate.proto";

option java_package = "io.envoyproxy.envoy.type.http.v3";
option java_outer_classname = "CookieProto";
option java_multiple_files = true;
option go_package = "github.com/envoyproxy/go-control-plane/envoy/type/http/v3;httpv3";
option (udpa.annotations.file_status).package_version_status = ACTIVE;

// [#protodoc-title: Http cookie API]

// Cookie defines an API for obtaining or generating HTTP cookie.
message Cookie {
// The name that will be used to obtain cookie value from downstream HTTP request or generate
// new cookie for downstream.
string name = 1 [(validate.rules).string = {min_len: 1}];

// Duration of cookie. This will be used to set the expiry time of a new cookie when it is
// generated. Set this to 0 to use a session cookie.
google.protobuf.Duration ttl = 2 [(validate.rules).duration = {gte {}}];

// Path of cookie. This will be used to set the path of a new cookie when it is generated.
// If no path is specified here, no path will be set for the cookie.
string path = 3;
}
2 changes: 2 additions & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ proto_library(
"//envoy/extensions/filters/http/rbac/v3:pkg",
"//envoy/extensions/filters/http/router/v3:pkg",
"//envoy/extensions/filters/http/set_metadata/v3:pkg",
"//envoy/extensions/filters/http/stateful_session/v3:pkg",
"//envoy/extensions/filters/http/tap/v3:pkg",
"//envoy/extensions/filters/http/wasm/v3:pkg",
"//envoy/extensions/filters/listener/http_inspector/v3:pkg",
Expand Down Expand Up @@ -148,6 +149,7 @@ proto_library(
"//envoy/extensions/http/header_formatters/preserve_case/v3:pkg",
"//envoy/extensions/http/original_ip_detection/custom_header/v3:pkg",
"//envoy/extensions/http/original_ip_detection/xff/v3:pkg",
"//envoy/extensions/http/stateful_session/cookie/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
"//envoy/extensions/internal_redirect/safe_cross_scheme/v3:pkg",
Expand Down
1 change: 1 addition & 0 deletions docs/root/api-v3/config/config.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Extensions
request_id/request_id
http/header_formatters
http/original_ip_detection
http/stateful_session
stat_sinks/stat_sinks
quic/quic_extensions
formatter/formatter
Expand Down
8 changes: 8 additions & 0 deletions docs/root/api-v3/config/http/stateful_session.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Stateful Session
=====================

.. toctree::
:glob:
:maxdepth: 2

../../extensions/http/stateful_session/*/v3/*
1 change: 1 addition & 0 deletions docs/root/api-v3/types/types.rst
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ Types
../type/v3/ratelimit_unit.proto
../type/v3/semantic_version.proto
../type/v3/token_bucket.proto
../type/http/v3/cookie.proto
../type/http/v3/path_transformation.proto
../type/matcher/v3/metadata.proto
../type/matcher/v3/node.proto
Expand Down
1 change: 1 addition & 0 deletions docs/root/configuration/http/http_filters/http_filters.rst
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,4 @@ HTTP filters
sxg_filter
tap_filter
wasm_filter
stateful_session_filter
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
.. _config_http_filters_stateful_session:

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.

I wonder if we should include a note in this documentation that this type of sticky session is an anti-pattern and that operators should avoid using this feature when possible?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think this function is not that special, and there are similar functions among other proxies. But I can add a note to let users use this feature with caution.

Similar function in other proxy: https://docs.nginx.com/nginx/admin-guide/load-balancer/http-load-balancer/#enabling-session-persistence

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.

Let's add it. Something like:

.. note::

 Stateful sessions can result in imbalanced load across upstreams and allow external actors to direct requests to
 specific upstream hosts. Operators should carefully consider the security and reliability implications of stateful 
 sessions before enabling this feature.


Stateful session
================

Stateful session is an HTTP filter which overrides the upstream host based on extensible session state
and updates the session state based on the final selected upstream host. The override host will
eventually overwrites the load balancing result. This filter implements session stickiness without using
a hash-based load balancer.

And by extending the session state, this filter also allows more flexible control over the results of
Comment thread
wbpcode marked this conversation as resolved.
the load balancing.

.. note::

Stateful sessions can result in imbalanced load across upstreams and allow external actors to direct
requests to specific upstream hosts. Operators should carefully consider the security and reliability
implications of stateful sessions before enabling this feature.

Overview
--------

Session stickiness allows requests belonging to the same session to be consistently routed to a specific
upstream host.

HTTP session stickiness in Envoy is generally achieved through hash-based load balancing.
The stickiness of hash-based sessions can be regarded as 'weak' since the upstream host may change when the
host set changes. This filter implements 'strong' stickiness. It is intended to handle the following cases:

* The case where more stable session stickiness is required. For example, when a host is marked as degraded
but it is desirable to continue routing requests for existing sessions to that host.
* The case where a non hash-based load balancer (Random, Round Robin, etc.) is used and session stickiness
is still required. If stateful sessions are enabled in this case, requests for new sessions will be routed
to the corresponding upstream host based on the result of load balancing. Requests belonging to existing
sessions will be routed to the session's upstream host.

Configuration
-------------

* :ref:`v3 API reference <envoy_v3_api_msg_extensions.filters.http.stateful_session.v3.StatefulSession>`
* This filter should be configured with the name *envoy.filters.http.stateful_session*.

How it works
------------

The most important configuration for this filter is an :ref:`extensible session state
<envoy_v3_api_field_extensions.filters.http.stateful_session.v3.StatefulSession.session_state>`.

While processing the request, the stateful session filter will search for the corresponding session and
host based on the request. The results of the search will be used to influence the final load balancing
results.

If no existing session is found, the filter will create a session to store the selected upstream host.
Please note that the session here is an abstract concept. The details of the storage are based on the
session state implementation.

One example
___________

Currently, only :ref:`cookie-based session state
<envoy_v3_api_msg_extensions.http.stateful_session.cookie.v3.CookieBasedSessionState>` is supported.
So let's take this as an example.

.. code-block:: yaml

name: envoy.filters.http.stateful_session
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.stateful_session.v3.StatefulSession
session_state:
name: envoy.http.stateful_session.cookie
typed_config:
"@type": type.googleapis.com/envoy.extensions.http.stateful_session.cookie.v3.CookieBasedSessionState
name: global-session-cookie
path: /path
ttl: 120s


In the above configuration, the cookie-based session state obtains the overridden host of the current session
from the cookie named `global-session-cookie` and if the corresponding host exists in the upstream cluster, the
request will be routed to that host.

If there is no valid cookie, the load balancer will choose a new upstream host. When responding, the address
of the selected upstream host will be stored in the cookie named `global-session-cookie`.
1 change: 1 addition & 0 deletions docs/root/version_history/current.rst
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Minor Behavior Changes
* listener: destroy per network filter chain stats when a network filter chain is removed during the listener in place update.
* quic: add back the support for IETF draft 29 which is guarded via ``envoy.reloadable_features.FLAGS_quic_reloadable_flag_quic_disable_version_draft_29``. It is off by default so Envoy only supports RFCv1 without flipping this runtime guard explicitly. Draft 29 is not recommended for use.
* router: take elapsed time into account when setting the x-envoy-expected-rq-timeout-ms header for retries, and never send a value that's longer than the request timeout. This behavioral change can be temporarily reverted by setting runtime guard ``envoy.reloadable_features.update_expected_rq_timeout_on_retry`` to false.
* stateful session http filter: added :ref:`stateful session http filter <config_http_filters_stateful_session>`.
* stream_info: response code details with empty space characters (' ', '\t', '\f', '\v', '\n', '\r') is not accepted by the ``setResponseCodeDetails()`` API.
* upstream: fixed a bug where auto_config didn't work for wrapped TLS sockets (e.g. if proxy proto were configured for TLS).

Expand Down
12 changes: 12 additions & 0 deletions envoy/http/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ envoy_cc_library(
"//envoy/ssl:connection_interface",
"//envoy/stream_info:stream_info_interface",
"//envoy/tracing:http_tracer_interface",
"//envoy/upstream:load_balancer_interface",
"//source/common/common:scope_tracked_object_stack",
],
)
Expand Down Expand Up @@ -182,3 +183,14 @@ envoy_cc_library(
"//envoy/server:factory_context_interface",
],
)

envoy_cc_library(
name = "stateful_session_interface",
hdrs = ["stateful_session.h"],
deps = [
":header_map_interface",
"//envoy/config:typed_config_interface",
"//envoy/server:factory_context_interface",
"//envoy/upstream:upstream_interface",
],
)
14 changes: 14 additions & 0 deletions envoy/http/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include "envoy/router/router.h"
#include "envoy/ssl/connection.h"
#include "envoy/tracing/http_tracer.h"
#include "envoy/upstream/load_balancer.h"
#include "envoy/upstream/upstream.h"

#include "source/common/common/scope_tracked_object_stack.h"
Expand Down Expand Up @@ -610,6 +611,19 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks {
*/
virtual void
requestRouteConfigUpdate(RouteConfigUpdatedCallbackSharedPtr route_config_updated_cb) PURE;

/**
* Set override host to be used by the upstream load balancing. If the target host exists in the
* host list of the routed cluster, the host should be selected first.
* @param host The override host address.
*/
virtual void setUpstreamOverrideHost(absl::string_view host) PURE;

/**
* @return absl::optional<absl::string_view> optional overrride host for the upstream
* load balancing.
*/
virtual absl::optional<absl::string_view> upstreamOverrideHost() const PURE;

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 you need this getter method on the decoder callbacks? Generally we want to keep this interface as minimal as possible as every filter implements it.

@wbpcode wbpcode Dec 30, 2021

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Router::Router will use this method to get the upstream override host address and pass the address to the upstream load balancer. And this interface can also be used by other custom http filters, such as doing some additional checks on the override host address.

Generally we want to keep this interface as minimal as possible as every filter implements it.

I'm not sure I understand what you mean. Only ActiveStreamDecoderFilter needs to implement this method. It is a general wrapper to HTTP filter. 🤔

};

/**
Expand Down
Loading