diff --git a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto index bc08198aa977..6e6f39eca697 100644 --- a/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto +++ b/api/envoy/extensions/filters/network/tcp_proxy/v3/tcp_proxy.proto @@ -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"; @@ -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 ` + // isn't true, it will be rejected. + string post_path = 5; } message OnDemand { diff --git a/changelogs/current.yaml b/changelogs/current.yaml index 2757c7b38a8c..1eaed37dbfde 100644 --- a/changelogs/current.yaml +++ b/changelogs/current.yaml @@ -148,6 +148,9 @@ new_features: - area: udp_proxy change: | added support for :ref:`proxy_access_log `. +- area: tcp_proxy + change: | + added new config :ref:`post_path field ` 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 ` to disable the active health check for the endpoint. diff --git a/envoy/tcp/upstream.h b/envoy/tcp/upstream.h index 0139f99a3424..3086007b0b6b 100644 --- a/envoy/tcp/upstream.h +++ b/envoy/tcp/upstream.h @@ -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; diff --git a/source/common/tcp_proxy/tcp_proxy.cc b/source/common/tcp_proxy/tcp_proxy.cc index c8beb5fe6e67..fc22377d4cff 100644 --- a/source/common/tcp_proxy/tcp_proxy.cc +++ b/source/common/tcp_proxy/tcp_proxy.cc @@ -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()); diff --git a/source/common/tcp_proxy/tcp_proxy.h b/source/common/tcp_proxy/tcp_proxy.h index 179780e630d6..6eda6693082a 100644 --- a/source/common/tcp_proxy/tcp_proxy.h +++ b/source/common/tcp_proxy/tcp_proxy.h @@ -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, @@ -147,6 +148,7 @@ class TunnelingConfigHelperImpl : public TunnelingConfigHelper, std::unique_ptr header_parser_; Formatter::FormatterPtr hostname_fmt_; const bool propagate_response_headers_; + std::string post_path_; }; /** diff --git a/source/common/tcp_proxy/upstream.cc b/source/common/tcp_proxy/upstream.cc index 6c12e0bdaec7..d669a10a316d 100644 --- a/source/common/tcp_proxy/upstream.cc +++ b/source/common/tcp_proxy/upstream.cc @@ -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, "/"); @@ -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, diff --git a/test/common/tcp_proxy/upstream_test.cc b/test/common/tcp_proxy/upstream_test.cc index 4190ccc2f57f..42ec5596f7cc 100644 --- a/test/common/tcp_proxy/upstream_test.cc +++ b/test/common/tcp_proxy/upstream_test.cc @@ -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 expected_headers; + expected_headers = Http::createHeaderMap({ + {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();