Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ extensions/filters/http/oauth2 @derekargueta @mattklein123
/*/extensions/access_loggers/file @wbpcode @cpakulski @giantcroc
# Stateful session
/*/extensions/http/stateful_session/cookie @wbpcode @cpakulski
/*/extensions/http/stateful_session/envelope @wbpcode @adisuissa
/*/extensions/http/stateful_session/header @ramaraochavali @wbpcode @cpakulski
/*/extensions/filters/http/stateful_session @wbpcode @cpakulski
# tracers
Expand Down
1 change: 1 addition & 0 deletions api/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ proto_library(
"//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/http/stateful_session/envelope/v3:pkg",
"//envoy/extensions/http/stateful_session/header/v3:pkg",
"//envoy/extensions/internal_redirect/allow_listed_routes/v3:pkg",
"//envoy/extensions/internal_redirect/previous_routes/v3:pkg",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ message StatefulSession {
//
// [#extension-category: envoy.http.stateful_session]
config.core.v3.TypedExtensionConfig session_state = 1;

// If set to True, the HTTP request must be routed to the requested destination.
// If the requested destination is not available, Envoy returns 503. Defaults to False.
bool strict = 2;
}

message StatefulSessionPerRoute {
Expand Down
9 changes: 9 additions & 0 deletions api/envoy/extensions/http/stateful_session/envelope/v3/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
# 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 = ["@com_github_cncf_udpa//udpa/annotations:pkg"],
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
syntax = "proto3";

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

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

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

// [#protodoc-title: Envelope stateful session extension]

// The extension allows the session state is tracked via existing session context that initialized
// by the upstream server. It assumes that the upstream server will generate the session context
// (like session id header or cookie) in the initial response of the session and the client will use
// the same session context in the subsequent requests without any modification.
//
// When processing the response from the upstream, Envoy will check if the response contains the
// session context. If the response contains the session context, no matter if it's a new session
// context or an existing one, Envoy will join it and the upstream host as new session context.
//
// When processing the request from the downstream, Envoy will check if the request contains the
// session context. If the request contains the session context, Envoy will strip the
// upstream host from the session context.
//
// [#extension: envoy.http.stateful_session.envelope]
message EnvelopeSessionState {
message Header {
// Iff the header specified by the ``name`` field is present in the response (assume the ``name``
// is set to ``session-header`` and original header value is ``xxxxxx``), then the upstream host
// address and value of ``name`` field specified header will be encoded in following format and
// the output will be used to update the ``name`` field specified header in the response:
//
// .. code-block:: none
//
// session-header: "MS4yLjMuNDo4MAo=;UV:eHh4eHh4Cg==" # base64(1.2.3.4:80);UV:base64(xxxxxx)
//
// The ``UV`` (upstream value) part is used to store the original upstream header value of
// ``name`` field specified header.
//
// If this mode is used then Envoy will assume that the header in the request will also be in the
// same format and will contain the ``UV`` part. This extension will parse the upstream host
// address and update the ``name`` field specified header in the request to the ``UV`` part.
string name = 1 [(validate.rules).string = {min_len: 1}];
}

// Set the header config to track the session state.
Header header = 1 [(validate.rules).message = {required: true}];
}
2 changes: 1 addition & 1 deletion api/envoy/type/http/v3/cookie.proto
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ message Cookie {
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.
// generated. Set this to 0s to use a session cookie and disable cookie expiration.
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.
Expand Down
1 change: 1 addition & 0 deletions api/versioning/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ proto_library(
"//envoy/extensions/http/original_ip_detection/xff/v3:pkg",
"//envoy/extensions/http/stateful_session/cookie/v3:pkg",
"//envoy/extensions/http/stateful_session/header/v3:pkg",
"//envoy/extensions/http/stateful_session/envelope/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
5 changes: 3 additions & 2 deletions envoy/http/filter.h
Original file line number Diff line number Diff line change
Expand Up @@ -761,13 +761,14 @@ class StreamDecoderFilterCallbacks : public virtual StreamFilterCallbacks,
* 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;
virtual void setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost) PURE;

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

#if defined(HIGRESS)
virtual bool needBuffering() const { return false; }
Expand Down
4 changes: 2 additions & 2 deletions envoy/http/stateful_session.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class SessionState {
* @param host the upstream host that was finally selected.
* @param headers the response headers.
*/
virtual void onUpdate(const Upstream::HostDescription& host, ResponseHeaderMap& headers) PURE;
virtual void onUpdate(absl::string_view host_address, ResponseHeaderMap& headers) PURE;
};

using SessionStatePtr = std::unique_ptr<SessionState>;
Expand All @@ -51,7 +51,7 @@ class SessionStateFactory {
*
* @param headers request headers.
*/
virtual SessionStatePtr create(const RequestHeaderMap& headers) const PURE;
virtual SessionStatePtr create(RequestHeaderMap& headers) const PURE;
};

using SessionStateFactorySharedPtr = std::shared_ptr<SessionStateFactory>;
Expand Down
2 changes: 1 addition & 1 deletion envoy/upstream/load_balancer.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ class LoadBalancerContext {
*/
virtual Network::TransportSocketOptionsConstSharedPtr upstreamTransportSocketOptions() const PURE;

using OverrideHost = absl::string_view;
using OverrideHost = std::pair<absl::string_view, bool>;
/**
* Returns the host the load balancer should select directly. If the expected host exists and
* the host can be selected directly, the load balancer can bypass the load balancing algorithm
Expand Down
2 changes: 2 additions & 0 deletions source/common/common/base64.cc
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ std::string Base64::encode(const Buffer::Instance& buffer, uint64_t length) {
return ret;
}

std::string Base64::encode(absl::string_view input) { return encode(input.data(), input.length()); }

std::string Base64::encode(const char* input, uint64_t length) {
return encode(input, length, true);
}
Expand Down
6 changes: 6 additions & 0 deletions source/common/common/base64.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@ class Base64 {
*/
static std::string encode(const char* input, uint64_t length);

/**
* Base64 encode an input char buffer with a given length.
* @param input string to encode.
*/
static std::string encode(absl::string_view input);

/**
* Base64 encode an input char buffer with a given length.
* @param input char array to encode.
Expand Down
7 changes: 5 additions & 2 deletions source/common/http/async_client_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -494,8 +494,11 @@ class AsyncStreamImpl : public virtual AsyncClient::Stream,
OptRef<DownstreamStreamFilterCallbacks> downstreamCallbacks() override { return {}; }
OptRef<UpstreamStreamFilterCallbacks> upstreamCallbacks() override { return {}; }
void resetIdleTimer() override {}
void setUpstreamOverrideHost(absl::string_view) override {}
absl::optional<absl::string_view> upstreamOverrideHost() const override { return {}; }
void setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost) override {}
absl::optional<Upstream::LoadBalancerContext::OverrideHost>
upstreamOverrideHost() const override {
return absl::nullopt;
}
absl::string_view filterConfigName() const override { return ""; }

// ScopeTrackedObject
Expand Down
9 changes: 5 additions & 4 deletions source/common/http/filter_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1756,11 +1756,12 @@ Buffer::BufferMemoryAccountSharedPtr ActiveStreamDecoderFilter::account() const
return parent_.account();
}

void ActiveStreamDecoderFilter::setUpstreamOverrideHost(absl::string_view host) {
parent_.upstream_override_host_.emplace(std::move(host));
void ActiveStreamDecoderFilter::setUpstreamOverrideHost(
Upstream::LoadBalancerContext::OverrideHost upstream_override_host) {
parent_.upstream_override_host_ = upstream_override_host;
}

absl::optional<absl::string_view> ActiveStreamDecoderFilter::upstreamOverrideHost() const {
absl::optional<Upstream::LoadBalancerContext::OverrideHost>
ActiveStreamDecoderFilter::upstreamOverrideHost() const {
return parent_.upstream_override_host_;
}

Expand Down
6 changes: 3 additions & 3 deletions source/common/http/filter_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,8 @@ struct ActiveStreamDecoderFilter : public ActiveStreamFilterBase,

Network::Socket::OptionsSharedPtr getUpstreamSocketOptions() const override;
Buffer::BufferMemoryAccountSharedPtr account() const override;
void setUpstreamOverrideHost(absl::string_view host) override;
absl::optional<absl::string_view> upstreamOverrideHost() const override;
void setUpstreamOverrideHost(Upstream::LoadBalancerContext::OverrideHost) override;
absl::optional<Upstream::LoadBalancerContext::OverrideHost> upstreamOverrideHost() const override;
#if defined(HIGRESS)
bool needBuffering() const override { return need_buffering_; }
void setNeedBuffering(bool need) override { need_buffering_ = need; }
Expand Down Expand Up @@ -1042,7 +1042,7 @@ class FilterManager : public ScopeTrackedObject,
std::list<DownstreamWatermarkCallbacks*> watermark_callbacks_;
Network::Socket::OptionsSharedPtr upstream_options_ =
std::make_shared<Network::Socket::Options>();
absl::optional<absl::string_view> upstream_override_host_;
absl::optional<Upstream::LoadBalancerContext::OverrideHost> upstream_override_host_;

const FilterChainFactory& filter_chain_factory_;
// TODO(snowp): Once FM has been moved to its own file we'll make these private classes of FM,
Expand Down
1 change: 0 additions & 1 deletion source/common/runtime/runtime_features.cc
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,6 @@ RUNTIME_GUARD(envoy_reloadable_features_send_header_raw_value);
RUNTIME_GUARD(envoy_reloadable_features_send_local_reply_when_no_buffer_and_upstream_request);
RUNTIME_GUARD(envoy_reloadable_features_service_sanitize_non_utf8_strings);
RUNTIME_GUARD(envoy_reloadable_features_skip_dns_lookup_for_proxied_requests);
RUNTIME_GUARD(envoy_reloadable_features_stateful_session_encode_ttl_in_cookie);
RUNTIME_GUARD(envoy_reloadable_features_successful_active_health_check_uneject_host);
RUNTIME_GUARD(envoy_reloadable_features_tcp_pool_idle_timeout);
RUNTIME_GUARD(envoy_reloadable_features_test_feature_true);
Expand Down
3 changes: 3 additions & 0 deletions source/common/upstream/cluster_manager_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1798,6 +1798,9 @@ HostConstSharedPtr ClusterManagerImpl::ThreadLocalClusterManagerImpl::ClusterEnt
if (host != nullptr) {
return host;
}
if (!HostUtility::allowLBChooseHost(context)) {
return nullptr;
}
return lb_->chooseHost(context);
}

Expand Down
14 changes: 13 additions & 1 deletion source/common/upstream/host_utility.cc
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ HostConstSharedPtr HostUtility::selectOverrideHost(const HostMap* host_map, Host
return nullptr;
}

auto host_iter = host_map->find(override_host.value());
auto host_iter = host_map->find(override_host.value().first);

// The override host cannot be found in the host map.
if (host_iter == host_map->end()) {
Expand All @@ -196,5 +196,17 @@ HostConstSharedPtr HostUtility::selectOverrideHost(const HostMap* host_map, Host
return nullptr;
}

bool HostUtility::allowLBChooseHost(LoadBalancerContext* context) {
if (context == nullptr) {
return true;
}
auto override_host = context->overrideHostToSelect();
if (!override_host.has_value()) {
return true;
}
// Return opposite value to "strict" setting.
return !override_host.value().second;
}

} // namespace Upstream
} // namespace Envoy
1 change: 1 addition & 0 deletions source/common/upstream/host_utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class HostUtility {
// A utility function to select override host from host map according to load balancer context.
static HostConstSharedPtr selectOverrideHost(const HostMap* host_map, HostStatusSet status,
LoadBalancerContext* context);
static bool allowLBChooseHost(LoadBalancerContext* context);
};

} // namespace Upstream
Expand Down
2 changes: 1 addition & 1 deletion source/extensions/common/wasm/context.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1993,7 +1993,7 @@ WasmResult Context::getUpstreamHosts(StringPairs* result) {
}
WasmResult Context::setUpstreamOverrideHost(std::string_view address) {
if (decoder_callbacks_) {
decoder_callbacks_->setUpstreamOverrideHost(address);
decoder_callbacks_->setUpstreamOverrideHost(std::make_pair(address, false));
}
return WasmResult::Ok;
}
Expand Down
6 changes: 3 additions & 3 deletions source/extensions/common/wasm/context.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@ using proxy_wasm::CloseType;
using proxy_wasm::ContextBase;
using proxy_wasm::Pairs;
using proxy_wasm::PairsWithStringValues;
using proxy_wasm::StringPairs;
using proxy_wasm::PluginBase;
using proxy_wasm::PluginHandleBase;
using proxy_wasm::SharedQueueDequeueToken;
using proxy_wasm::SharedQueueEnqueueToken;
using proxy_wasm::StringPairs;
using proxy_wasm::WasmBase;
using proxy_wasm::WasmBufferType;
using proxy_wasm::WasmHandleBase;
Expand Down Expand Up @@ -221,10 +221,10 @@ class Context : public proxy_wasm::ContextBase,
#if defined(HIGRESS)
WasmResult injectEncodedDataToFilterChain(std::string_view body_text, bool end_stream) override;
WasmResult injectEncodedDataToFilterChainOnHeader(std::string_view body_text, bool end_stream);
WasmResult getUpstreamHosts(StringPairs * result) override;
WasmResult getUpstreamHosts(StringPairs* result) override;
WasmResult setUpstreamOverrideHost(std::string_view address) override;
#endif

void clearRouteCache() override {
#if defined(HIGRESS)
if (decoder_callbacks_ && !disable_clear_route_cache_) {
Expand Down
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,7 @@ EXTENSIONS = {
#

"envoy.http.stateful_session.cookie": "//source/extensions/http/stateful_session/cookie:config",
"envoy.http.stateful_session.envelope": "//source/extensions/http/stateful_session/envelope:config",
"envoy.http.stateful_session.header": "//source/extensions/http/stateful_session/header:config",

#
Expand Down
7 changes: 7 additions & 0 deletions source/extensions/extensions_metadata.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1337,6 +1337,13 @@ envoy.http.stateful_session.header:
status: alpha
type_urls:
- envoy.extensions.http.stateful_session.header.v3.HeaderBasedSessionState
envoy.http.stateful_session.envelope:
categories:
- envoy.http.stateful_session
security_posture: unknown
status: alpha
type_urls:
- envoy.extensions.http.stateful_session.envelope.v3.EnvelopeSessionState
envoy.matching.inputs.request_headers:
categories:
- envoy.matching.http.input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,16 @@ namespace {

class EmptySessionStateFactory : public Envoy::Http::SessionStateFactory {
public:
Envoy::Http::SessionStatePtr create(const Envoy::Http::RequestHeaderMap&) const override {
Envoy::Http::SessionStatePtr create(Envoy::Http::RequestHeaderMap&) const override {
return nullptr;
}
};

} // namespace

StatefulSessionConfig::StatefulSessionConfig(const ProtoConfig& config,
Server::Configuration::CommonFactoryContext& context) {
Server::Configuration::CommonFactoryContext& context)
: strict_(config.strict()) {
if (!config.has_session_state()) {
factory_ = std::make_shared<EmptySessionStateFactory>();
return;
Expand Down Expand Up @@ -66,7 +67,8 @@ Http::FilterHeadersStatus StatefulSession::decodeHeaders(Http::RequestHeaderMap&
}

if (auto upstream_address = session_state_->upstreamAddress(); upstream_address.has_value()) {
decoder_callbacks_->setUpstreamOverrideHost(upstream_address.value());
decoder_callbacks_->setUpstreamOverrideHost(
std::make_pair(upstream_address.value(), config->isStrict()));
}
return Http::FilterHeadersStatus::Continue;
}
Expand All @@ -80,7 +82,7 @@ Http::FilterHeadersStatus StatefulSession::encodeHeaders(Http::ResponseHeaderMap
upstream_info != nullptr) {
auto host = upstream_info->upstreamHost();
if (host != nullptr) {
session_state_->onUpdate(*host, headers);
session_state_->onUpdate(host->address()->asStringView(), headers);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ class StatefulSessionConfig {
StatefulSessionConfig(const ProtoConfig& config,
Server::Configuration::CommonFactoryContext& context);

Http::SessionStatePtr createSessionState(const Http::RequestHeaderMap& headers) const {
Http::SessionStatePtr createSessionState(Http::RequestHeaderMap& headers) const {
ASSERT(factory_ != nullptr);
return factory_->create(headers);
}

bool isStrict() const { return strict_; }

private:
Http::SessionStateFactorySharedPtr factory_;
bool strict_{false};
};
using StatefulSessionConfigSharedPtr = std::shared_ptr<StatefulSessionConfig>;

Expand Down Expand Up @@ -68,7 +71,7 @@ class StatefulSession : public Http::PassThroughFilter,
private:
Http::SessionStatePtr session_state_;

StatefulSessionConfigSharedPtr config_{};
StatefulSessionConfigSharedPtr config_;
};

} // namespace StatefulSession
Expand Down
Loading