11#include " source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.h"
22
33#include " envoy/buffer/buffer.h"
4+ #include " envoy/config/core/v3/substitution_format_string.pb.h"
45#include " envoy/network/connection.h"
56#include " envoy/server/overload/overload_manager.h"
67
78#include " source/common/buffer/buffer_impl.h"
9+ #include " source/common/config/datasource.h"
10+ #include " source/common/formatter/substitution_format_string.h"
11+ #include " source/common/formatter/substitution_formatter.h"
812#include " source/common/http/codes.h"
913#include " source/common/http/header_map_impl.h"
1014#include " source/common/http/headers.h"
1519#include " source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h"
1620#include " source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h"
1721#include " source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h"
22+ #include " source/server/generic_factory_context.h"
1823
1924namespace Envoy {
2025namespace Extensions {
@@ -29,9 +34,59 @@ ReverseTunnelFilter::ReverseTunnelStats::generateStats(const std::string& prefix
2934}
3035
3136// ReverseTunnelFilterConfig implementation.
37+ absl::StatusOr<std::shared_ptr<ReverseTunnelFilterConfig>> ReverseTunnelFilterConfig::create (
38+ const envoy::extensions::filters::network::reverse_tunnel::v3::ReverseTunnel& proto_config,
39+ Server::Configuration::FactoryContext& context) {
40+
41+ Formatter::FormatterConstSharedPtr node_id_formatter;
42+ Formatter::FormatterConstSharedPtr cluster_id_formatter;
43+
44+ // Create formatters for validation if configured.
45+ if (proto_config.has_validation ()) {
46+ Server::GenericFactoryContextImpl generic_context (context.serverFactoryContext (),
47+ context.messageValidationVisitor ());
48+
49+ const auto & validation = proto_config.validation ();
50+
51+ // Create node_id formatter if configured.
52+ if (!validation.node_id_format ().empty ()) {
53+ envoy::config::core::v3::SubstitutionFormatString node_id_format_config;
54+ node_id_format_config.mutable_text_format_source ()->set_inline_string (
55+ validation.node_id_format ());
56+
57+ auto formatter_or_error = Formatter::SubstitutionFormatStringUtils::fromProtoConfig (
58+ node_id_format_config, generic_context);
59+ if (!formatter_or_error.ok ()) {
60+ return absl::InvalidArgumentError (fmt::format (" Failed to parse node_id_format: {}" ,
61+ formatter_or_error.status ().message ()));
62+ }
63+ node_id_formatter = std::move (formatter_or_error.value ());
64+ }
65+
66+ // Create cluster_id formatter if configured.
67+ if (!validation.cluster_id_format ().empty ()) {
68+ envoy::config::core::v3::SubstitutionFormatString cluster_id_format_config;
69+ cluster_id_format_config.mutable_text_format_source ()->set_inline_string (
70+ validation.cluster_id_format ());
71+
72+ auto formatter_or_error = Formatter::SubstitutionFormatStringUtils::fromProtoConfig (
73+ cluster_id_format_config, generic_context);
74+ if (!formatter_or_error.ok ()) {
75+ return absl::InvalidArgumentError (fmt::format (" Failed to parse cluster_id_format: {}" ,
76+ formatter_or_error.status ().message ()));
77+ }
78+ cluster_id_formatter = std::move (formatter_or_error.value ());
79+ }
80+ }
81+
82+ return std::shared_ptr<ReverseTunnelFilterConfig>(new ReverseTunnelFilterConfig (
83+ proto_config, std::move (node_id_formatter), std::move (cluster_id_formatter)));
84+ }
85+
3286ReverseTunnelFilterConfig::ReverseTunnelFilterConfig (
3387 const envoy::extensions::filters::network::reverse_tunnel::v3::ReverseTunnel& proto_config,
34- Server::Configuration::FactoryContext&)
88+ Formatter::FormatterConstSharedPtr node_id_formatter,
89+ Formatter::FormatterConstSharedPtr cluster_id_formatter)
3590 : ping_interval_(proto_config.has_ping_interval()
3691 ? std::chrono::milliseconds(
3792 DurationUtil::durationToMilliseconds (proto_config.ping_interval()))
@@ -46,7 +101,77 @@ ReverseTunnelFilterConfig::ReverseTunnelFilterConfig(
46101 method = envoy::config::core::v3::GET;
47102 }
48103 return envoy::config::core::v3::RequestMethod_Name (method);
49- }()) {}
104+ }()),
105+ node_id_formatter_ (std::move(node_id_formatter)),
106+ cluster_id_formatter_(std::move(cluster_id_formatter)),
107+ emit_dynamic_metadata_(proto_config.has_validation() &&
108+ proto_config.validation().emit_dynamic_metadata()),
109+ dynamic_metadata_namespace_(
110+ proto_config.has_validation() &&
111+ !proto_config.validation().dynamic_metadata_namespace().empty()
112+ ? proto_config.validation().dynamic_metadata_namespace()
113+ : "envoy.filters.network.reverse_tunnel") {}
114+
115+ bool ReverseTunnelFilterConfig::validateIdentifiers (
116+ absl::string_view node_id, absl::string_view cluster_id,
117+ const StreamInfo::StreamInfo& stream_info) const {
118+
119+ // If no validation configured, pass validation.
120+ if (!node_id_formatter_ && !cluster_id_formatter_) {
121+ return true ;
122+ }
123+
124+ // Validate node_id if formatter is configured.
125+ if (node_id_formatter_) {
126+ const std::string expected_node_id = node_id_formatter_->formatWithContext ({}, stream_info);
127+ if (!expected_node_id.empty () && expected_node_id != node_id) {
128+ ENVOY_LOG (debug, " reverse_tunnel: node_id validation failed. Expected: '{}', Actual: '{}'" ,
129+ expected_node_id, node_id);
130+ return false ;
131+ }
132+ }
133+
134+ // Validate cluster_id if formatter is configured.
135+ if (cluster_id_formatter_) {
136+ const std::string expected_cluster_id =
137+ cluster_id_formatter_->formatWithContext ({}, stream_info);
138+ if (!expected_cluster_id.empty () && expected_cluster_id != cluster_id) {
139+ ENVOY_LOG (debug, " reverse_tunnel: cluster_id validation failed. Expected: '{}', Actual: '{}'" ,
140+ expected_cluster_id, cluster_id);
141+ return false ;
142+ }
143+ }
144+
145+ return true ;
146+ }
147+
148+ void ReverseTunnelFilterConfig::emitValidationMetadata (absl::string_view node_id,
149+ absl::string_view cluster_id,
150+ bool validation_passed,
151+ StreamInfo::StreamInfo& stream_info) const {
152+ if (!emit_dynamic_metadata_) {
153+ return ;
154+ }
155+
156+ Protobuf::Struct metadata;
157+ auto & fields = *metadata.mutable_fields ();
158+
159+ // Emit actual identifiers.
160+ fields[" node_id" ].set_string_value (std::string (node_id));
161+ fields[" cluster_id" ].set_string_value (std::string (cluster_id));
162+
163+ // Emit validation result.
164+ fields[" validation_result" ].set_string_value (validation_passed ? " allowed" : " denied" );
165+
166+ // Set dynamic metadata on the stream info.
167+ stream_info.setDynamicMetadata (dynamic_metadata_namespace_, metadata);
168+
169+ ENVOY_LOG (trace,
170+ " reverse_tunnel: emitted dynamic metadata to namespace '{}': node_id={}, "
171+ " cluster_id={}, validation_result={}" ,
172+ dynamic_metadata_namespace_, node_id, cluster_id,
173+ validation_passed ? " allowed" : " denied" );
174+ }
50175
51176// ReverseTunnelFilter implementation.
52177ReverseTunnelFilter::ReverseTunnelFilter (ReverseTunnelFilterConfigSharedPtr config,
@@ -191,6 +316,25 @@ void ReverseTunnelFilter::RequestDecoderImpl::processIfComplete(bool end_stream)
191316 const absl::string_view cluster_id = cluster_vals[0 ]->value ().getStringView ();
192317 const absl::string_view tenant_id = tenant_vals[0 ]->value ().getStringView ();
193318
319+ // Validate node_id and cluster_id if validation is configured.
320+ auto & connection = parent_.read_callbacks_ ->connection ();
321+ const bool validation_passed =
322+ parent_.config_ ->validateIdentifiers (node_id, cluster_id, connection.streamInfo ());
323+
324+ // Emit validation metadata if configured.
325+ parent_.config_ ->emitValidationMetadata (node_id, cluster_id, validation_passed,
326+ connection.streamInfo ());
327+
328+ if (!validation_passed) {
329+ parent_.stats_ .validation_failed_ .inc ();
330+ ENVOY_CONN_LOG (debug, " reverse_tunnel: validation failed for node '{}', cluster '{}'" ,
331+ parent_.read_callbacks_ ->connection (), node_id, cluster_id);
332+ sendLocalReply (Http::Code::Forbidden, " Validation failed" , nullptr , absl::nullopt ,
333+ " reverse_tunnel_validation_failed" );
334+ parent_.read_callbacks_ ->connection ().close (Network::ConnectionCloseType::FlushWrite);
335+ return ;
336+ }
337+
194338 // Respond with 200 OK.
195339 auto resp_headers = Http::ResponseHeaderMapImpl::create ();
196340 resp_headers->setStatus (200 );
0 commit comments