1
1
#include " source/extensions/filters/network/reverse_tunnel/reverse_tunnel_filter.h"
2
2
3
3
#include " envoy/buffer/buffer.h"
4
+ #include " envoy/config/core/v3/substitution_format_string.pb.h"
4
5
#include " envoy/network/connection.h"
5
6
#include " envoy/server/overload/overload_manager.h"
6
7
7
8
#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"
8
12
#include " source/common/http/codes.h"
9
13
#include " source/common/http/header_map_impl.h"
10
14
#include " source/common/http/headers.h"
15
19
#include " source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor.h"
16
20
#include " source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/reverse_tunnel_acceptor_extension.h"
17
21
#include " source/extensions/bootstrap/reverse_tunnel/upstream_socket_interface/upstream_socket_manager.h"
22
+ #include " source/server/generic_factory_context.h"
18
23
19
24
namespace Envoy {
20
25
namespace Extensions {
@@ -29,9 +34,59 @@ ReverseTunnelFilter::ReverseTunnelStats::generateStats(const std::string& prefix
29
34
}
30
35
31
36
// 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
+
32
86
ReverseTunnelFilterConfig::ReverseTunnelFilterConfig (
33
87
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)
35
90
: ping_interval_(proto_config.has_ping_interval()
36
91
? std::chrono::milliseconds(
37
92
DurationUtil::durationToMilliseconds (proto_config.ping_interval()))
@@ -46,7 +101,78 @@ ReverseTunnelFilterConfig::ReverseTunnelFilterConfig(
46
101
method = envoy::config::core::v3::GET;
47
102
}
48
103
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 (
149
+ absl::string_view node_id, absl::string_view cluster_id, bool validation_passed,
150
+ const StreamInfo::StreamInfo& stream_info) const {
151
+ if (!emit_dynamic_metadata_) {
152
+ return ;
153
+ }
154
+
155
+ Protobuf::Struct metadata;
156
+ auto & fields = *metadata.mutable_fields ();
157
+
158
+ // Emit actual identifiers.
159
+ fields[" node_id" ].set_string_value (std::string (node_id));
160
+ fields[" cluster_id" ].set_string_value (std::string (cluster_id));
161
+
162
+ // Emit validation result.
163
+ fields[" validation_result" ].set_string_value (validation_passed ? " allowed" : " denied" );
164
+
165
+ // StreamInfo::setDynamicMetadata is not const, so we need to cast away constness.
166
+ // This is safe because we're modifying dynamic metadata, which is mutable by design.
167
+ const_cast <StreamInfo::StreamInfo&>(stream_info)
168
+ .setDynamicMetadata (dynamic_metadata_namespace_, metadata);
169
+
170
+ ENVOY_LOG (trace,
171
+ " reverse_tunnel: emitted dynamic metadata to namespace '{}': node_id={}, "
172
+ " cluster_id={}, validation_result={}" ,
173
+ dynamic_metadata_namespace_, node_id, cluster_id,
174
+ validation_passed ? " allowed" : " denied" );
175
+ }
50
176
51
177
// ReverseTunnelFilter implementation.
52
178
ReverseTunnelFilter::ReverseTunnelFilter (ReverseTunnelFilterConfigSharedPtr config,
@@ -191,6 +317,25 @@ void ReverseTunnelFilter::RequestDecoderImpl::processIfComplete(bool end_stream)
191
317
const absl::string_view cluster_id = cluster_vals[0 ]->value ().getStringView ();
192
318
const absl::string_view tenant_id = tenant_vals[0 ]->value ().getStringView ();
193
319
320
+ // Validate node_id and cluster_id if validation is configured.
321
+ const auto & connection = parent_.read_callbacks_ ->connection ();
322
+ const bool validation_passed =
323
+ parent_.config_ ->validateIdentifiers (node_id, cluster_id, connection.streamInfo ());
324
+
325
+ // Emit validation metadata if configured.
326
+ parent_.config_ ->emitValidationMetadata (node_id, cluster_id, validation_passed,
327
+ connection.streamInfo ());
328
+
329
+ if (!validation_passed) {
330
+ parent_.stats_ .validation_failed_ .inc ();
331
+ ENVOY_CONN_LOG (debug, " reverse_tunnel: validation failed for node '{}', cluster '{}'" ,
332
+ parent_.read_callbacks_ ->connection (), node_id, cluster_id);
333
+ sendLocalReply (Http::Code::Forbidden, " Validation failed" , nullptr , absl::nullopt ,
334
+ " reverse_tunnel_validation_failed" );
335
+ parent_.read_callbacks_ ->connection ().close (Network::ConnectionCloseType::FlushWrite);
336
+ return ;
337
+ }
338
+
194
339
// Respond with 200 OK.
195
340
auto resp_headers = Http::ResponseHeaderMapImpl::create ();
196
341
resp_headers->setStatus (200 );
0 commit comments