Skip to content

Commit

Permalink
tcp proxy: enable to specify a path for HTTP tunneling with POST meth…
Browse files Browse the repository at this point in the history
…od (#24045)

Risk Level: low
Testing: unittest
Docs Changes: API doc
Release Notes: new feature
Fixes #24038

Signed-off-by: He Jie Xu <hejie.xu@intel.com>
  • Loading branch information
soulxu authored Nov 28, 2022
1 parent 8f385e0 commit ea2cdc3
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ message TcpProxy {
// Configuration for tunneling TCP over other transports or application layers.
// Tunneling is supported over both HTTP/1.1 and HTTP/2. Upstream protocol is
// determined by the cluster configuration.
// [#next-free-field: 6]
message TunnelingConfig {
option (udpa.annotations.versioning).previous_message_type =
"envoy.config.filter.network.tcp_proxy.v2.TcpProxy.TunnelingConfig";
Expand Down Expand Up @@ -103,6 +104,11 @@ message TcpProxy {
// Save the response headers to the downstream info filter state for consumption
// by the network filters. The filter state key is ``envoy.tcp_proxy.propagate_response_headers``.
bool propagate_response_headers = 4;

// The path used with POST method. Default path is ``/``. If post path is specified and
// :ref:`use_post field <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.TunnelingConfig.use_post>`
// isn't true, it will be rejected.
string post_path = 5;
}

message OnDemand {
Expand Down
3 changes: 3 additions & 0 deletions changelogs/current.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,9 @@ new_features:
- area: udp_proxy
change: |
added support for :ref:`proxy_access_log <envoy_v3_api_field_extensions.filters.udp.udp_proxy.v3.UdpProxyConfig.proxy_access_log>`.
- area: tcp_proxy
change: |
added new config :ref:`post_path field <envoy_v3_api_field_extensions.filters.network.tcp_proxy.v3.TcpProxy.TunnelingConfig.post_path>` to specifiy a custom path for HTTP tunneling with POST method.
- area: health_check
change: |
added an optional bool flag :ref:`disable_active_health_check <envoy_v3_api_field_config.endpoint.v3.Endpoint.HealthCheckConfig.disable_active_health_check>` to disable the active health check for the endpoint.
Expand Down
3 changes: 3 additions & 0 deletions envoy/tcp/upstream.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class TunnelingConfigHelper {
// The method of the upstream HTTP request. True if using POST method, CONNECT otherwise.
virtual bool usePost() const PURE;

// The path used for POST method.
virtual const std::string& postPath() const PURE;

// The evaluator to add additional HTTP request headers to the upstream request.
virtual Envoy::Http::HeaderEvaluator& headerEvaluator() const PURE;

Expand Down
8 changes: 7 additions & 1 deletion source/common/tcp_proxy/tcp_proxy.cc
Original file line number Diff line number Diff line change
Expand Up @@ -576,7 +576,13 @@ TunnelingConfigHelperImpl::TunnelingConfigHelperImpl(
Server::Configuration::FactoryContext& context)
: use_post_(config_message.use_post()),
header_parser_(Envoy::Router::HeaderParser::configure(config_message.headers_to_add())),
propagate_response_headers_(config_message.propagate_response_headers()) {
propagate_response_headers_(config_message.propagate_response_headers()),
post_path_(config_message.post_path()) {
if (!post_path_.empty() && !use_post_) {
throw EnvoyException("Can't set a post path when POST method isn't used");
}
post_path_ = post_path_.empty() ? "/" : post_path_;

envoy::config::core::v3::SubstitutionFormatString substitution_format_config;
substitution_format_config.mutable_text_format_source()->set_inline_string(
config_message.hostname());
Expand Down
2 changes: 2 additions & 0 deletions source/common/tcp_proxy/tcp_proxy.h
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ class TunnelingConfigHelperImpl : public TunnelingConfigHelper,
Server::Configuration::FactoryContext& context);
std::string host(const StreamInfo::StreamInfo& stream_info) const override;
bool usePost() const override { return use_post_; }
const std::string& postPath() const override { return post_path_; }
Envoy::Http::HeaderEvaluator& headerEvaluator() const override { return *header_parser_; }
void
propagateResponseHeaders(Http::ResponseHeaderMapPtr&& headers,
Expand All @@ -147,6 +148,7 @@ class TunnelingConfigHelperImpl : public TunnelingConfigHelper,
std::unique_ptr<Envoy::Router::HeaderParser> header_parser_;
Formatter::FormatterPtr hostname_fmt_;
const bool propagate_response_headers_;
std::string post_path_;
};

/**
Expand Down
4 changes: 2 additions & 2 deletions source/common/tcp_proxy/upstream.cc
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ void Http2Upstream::setRequestEncoder(Http::RequestEncoder& request_encoder, boo
});

if (config_.usePost()) {
headers->addReference(Http::Headers::get().Path, "/");
headers->addReference(Http::Headers::get().Path, config_.postPath());
headers->addReference(Http::Headers::get().Scheme, scheme);
} else if (!Runtime::runtimeFeatureEnabled("envoy.reloadable_features.use_rfc_connect")) {
headers->addReference(Http::Headers::get().Path, "/");
Expand Down Expand Up @@ -325,7 +325,7 @@ void Http1Upstream::setRequestEncoder(Http::RequestEncoder& request_encoder, boo

if (config_.usePost()) {
// Path is required for POST requests.
headers->addReference(Http::Headers::get().Path, "/");
headers->addReference(Http::Headers::get().Path, config_.postPath());
}

config_.headerEvaluator().evaluateHeaders(*headers,
Expand Down
27 changes: 27 additions & 0 deletions test/common/tcp_proxy/upstream_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,33 @@ TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderUsePost) {
this->upstream_->setRequestEncoder(this->encoder_, false);
}

TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderUsePostWithCustomPath) {
this->config_message_.set_use_post(true);
this->config_message_.set_post_path("/test");
this->setupUpstream();
std::unique_ptr<Http::RequestHeaderMapImpl> expected_headers;
expected_headers = Http::createHeaderMap<Http::RequestHeaderMapImpl>({
{Http::Headers::get().Method, "POST"},
{Http::Headers::get().Host, this->config_->host(this->downstream_stream_info_)},
{Http::Headers::get().Path, "/test"},
});

if (this->is_http2_) {
expected_headers->addReference(Http::Headers::get().Scheme,
Http::Headers::get().SchemeValues.Http);
}

EXPECT_CALL(this->encoder_, encodeHeaders(HeaderMapEqualRef(expected_headers.get()), false));
this->upstream_->setRequestEncoder(this->encoder_, false);
}

TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderConnectWithCustomPath) {
this->config_message_.set_use_post(false);
this->config_message_.set_post_path("/test");
EXPECT_THROW_WITH_MESSAGE(this->setupUpstream(), EnvoyException,
"Can't set a post path when POST method isn't used");
}

TYPED_TEST(HttpUpstreamRequestEncoderTest, RequestEncoderHeaders) {
auto* header = this->config_message_.add_headers_to_add();
auto* hdr = header->mutable_header();
Expand Down

0 comments on commit ea2cdc3

Please sign in to comment.