Skip to content

Commit 8863154

Browse files
committed
reverse_tunnels: add validation in the network filter
Signed-off-by: Rohit Agrawal <[email protected]>
1 parent b0059c0 commit 8863154

File tree

11 files changed

+720
-26
lines changed

11 files changed

+720
-26
lines changed

api/envoy/extensions/filters/network/reverse_tunnel/v3/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ api_proto_package(
88
deps = [
99
"//envoy/config/core/v3:pkg",
1010
"@com_github_cncf_xds//udpa/annotations:pkg",
11+
"@com_github_cncf_xds//xds/type/matcher/v3:pkg",
1112
],
1213
)

api/envoy/extensions/filters/network/reverse_tunnel/v3/reverse_tunnel.proto

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import "envoy/config/core/v3/base.proto";
66

77
import "google/protobuf/duration.proto";
88

9+
import "xds/type/matcher/v3/matcher.proto";
10+
911
import "udpa/annotations/status.proto";
1012
import "validate/validate.proto";
1113

@@ -19,9 +21,18 @@ option (udpa.annotations.file_status).package_version_status = ACTIVE;
1921
// Reverse Tunnel Network Filter :ref:`configuration overview <config_network_filters_reverse_tunnel>`.
2022
// [#extension: envoy.filters.network.reverse_tunnel]
2123

22-
// Configuration for the reverse tunnel network filter.
23-
// This filter handles reverse tunnel connection acceptance and rejection by processing
24+
// The Reverse Tunnel filter handles reverse tunnel connection acceptance and rejection by processing
2425
// HTTP requests where required identification values are provided via HTTP headers.
26+
//
27+
// The filter operates as a terminal filter when processing reverse tunnel requests, meaning it
28+
// stops the filter chain after processing and manages connection lifecycle. It extracts node ID,
29+
// cluster ID, and tenant ID from HTTP headers and optionally validates these values using the
30+
// generic matcher framework.
31+
//
32+
// The filter supports configurable validation rules that can match against the extracted identifiers
33+
// using various matcher types including string matching, regular expressions, and custom matchers.
34+
// Requests that fail validation are rejected with HTTP 403 Forbidden.
35+
// [#next-free-field: 6]
2536
message ReverseTunnel {
2637
// Ping interval for health checks on established reverse tunnel connections.
2738
// If not specified, defaults to 2 seconds.
@@ -31,15 +42,70 @@ message ReverseTunnel {
3142
}];
3243

3344
// Whether to automatically close connections after processing reverse tunnel requests.
34-
// When set to true, connections are closed after acceptance or rejection.
35-
// When set to false, connections remain open for potential reuse. Defaults to false.
45+
// When set to ``true``, connections are closed after acceptance or rejection.
46+
// When set to ``false``, connections remain open for potential reuse. Defaults to ``false``.
3647
bool auto_close_connections = 2;
3748

3849
// HTTP path to match for reverse tunnel requests.
39-
// If not specified, defaults to "/reverse_connections/request".
50+
// If not specified, defaults to ``/reverse_connections/request``.
4051
string request_path = 3 [(validate.rules).string = {min_len: 1 max_len: 255 ignore_empty: true}];
4152

4253
// HTTP method to match for reverse tunnel requests.
4354
// If not specified (``METHOD_UNSPECIFIED``), this defaults to ``GET``.
4455
config.core.v3.RequestMethod request_method = 4 [(validate.rules).enum = {defined_only: true}];
56+
57+
// Optional validation matcher to apply to node and cluster identifiers using the generic matcher framework.
58+
// If specified, the matcher will be evaluated against the extracted ``node_id`` and ``cluster_id`` from
59+
// the incoming reverse connection handshake. Requests that fail validation will be rejected with
60+
// HTTP ``403 Forbidden``.
61+
//
62+
// The matcher can use various input types including:
63+
//
64+
// * Custom inputs like ``envoy.matching.inputs.reverse_tunnel_node_id`` and
65+
// ``envoy.matching.inputs.reverse_tunnel_cluster_id`` to match against the extracted identifiers.
66+
// * Standard inputs like dynamic metadata, filter state, or connection properties.
67+
// * String matching, regular expressions, or custom matcher extensions.
68+
//
69+
// Example configuration for validating both ``node_id`` and ``cluster_id``:
70+
//
71+
// .. code-block:: yaml
72+
//
73+
// validation_matcher:
74+
// matcher_list:
75+
// matchers:
76+
// - predicate:
77+
// and_matcher:
78+
// predicate:
79+
// - predicate:
80+
// single_predicate:
81+
// input:
82+
// name: envoy.matching.inputs.reverse_tunnel_node_id
83+
// typed_config:
84+
// "@type": type.googleapis.com/envoy.extensions.filters.network.reverse_tunnel.v3.NodeIdInput
85+
// value_match:
86+
// exact: expected-node
87+
// - predicate:
88+
// single_predicate:
89+
// input:
90+
// name: envoy.matching.inputs.reverse_tunnel_cluster_id
91+
// typed_config:
92+
// "@type": type.googleapis.com/envoy.extensions.filters.network.reverse_tunnel.v3.ClusterIdInput
93+
// value_match:
94+
// prefix: cluster-
95+
// on_match:
96+
// action:
97+
// name: skip
98+
// typed_config:
99+
// "@type": type.googleapis.com/envoy.extensions.filters.common.matcher.action.v3.SkipFilter
100+
xds.type.matcher.v3.Matcher validation_matcher = 5;
101+
}
102+
103+
// Custom input for the generic matcher that provides the node ID value
104+
// extracted from the ``x-envoy-reverse-tunnel-node-id`` HTTP header.
105+
message NodeIdInput {
106+
}
107+
108+
// Custom input for the generic matcher that provides the cluster ID value
109+
// extracted from the ``x-envoy-reverse-tunnel-cluster-id`` HTTP header.
110+
message ClusterIdInput {
45111
}

source/extensions/filters/network/reverse_tunnel/BUILD

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,32 @@ envoy_cc_extension(
2121
],
2222
)
2323

24+
envoy_cc_library(
25+
name = "inputs_lib",
26+
srcs = ["inputs.cc"],
27+
hdrs = ["inputs.h"],
28+
deps = [
29+
"//envoy/matcher:matcher_interface",
30+
"//envoy/server:factory_context_interface",
31+
"//source/common/network/matching:data_impl_lib",
32+
"@envoy_api//envoy/extensions/filters/network/reverse_tunnel/v3:pkg_cc_proto",
33+
],
34+
)
35+
2436
envoy_cc_library(
2537
name = "reverse_tunnel_filter_lib",
26-
srcs = ["reverse_tunnel_filter.cc"],
27-
hdrs = ["reverse_tunnel_filter.h"],
38+
srcs = [
39+
"inputs.cc",
40+
"reverse_tunnel_filter.cc",
41+
],
42+
hdrs = [
43+
"inputs.h",
44+
"reverse_tunnel_filter.h",
45+
],
2846
deps = [
2947
"//envoy/buffer:buffer_interface",
3048
"//envoy/http:codec_interface",
49+
"//envoy/matcher:matcher_interface",
3150
"//envoy/network:connection_interface",
3251
"//envoy/network:filter_interface",
3352
"//envoy/ssl:connection_interface",
@@ -41,7 +60,9 @@ envoy_cc_library(
4160
"//source/common/http/http1:codec_lib",
4261
"//source/common/http/http1:codec_stats_lib",
4362
"//source/common/http/http1:settings_lib",
63+
"//source/common/matcher:matcher_lib",
4464
"//source/common/network:connection_socket_lib",
65+
"//source/common/network/matching:data_impl_lib",
4566
"//source/common/protobuf",
4667
"//source/common/protobuf:message_validator_lib",
4768
"//source/common/protobuf:utility_lib",
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#include "source/extensions/filters/network/reverse_tunnel/inputs.h"
2+
3+
#include "source/common/protobuf/utility.h"
4+
5+
namespace Envoy {
6+
namespace Extensions {
7+
namespace NetworkFilters {
8+
namespace ReverseTunnel {
9+
10+
Matcher::DataInputGetResult NodeIdInput::get(const Network::MatchingData& data) const {
11+
// Try to cast to our custom matching data implementation.
12+
const auto* rt_data = dynamic_cast<const ReverseTunnelMatchingDataImpl*>(&data);
13+
if (rt_data != nullptr) {
14+
return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable,
15+
std::string(rt_data->nodeId())};
16+
}
17+
18+
// If we can't get the data, return no data (monostate).
19+
return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::monostate()};
20+
}
21+
22+
Matcher::DataInputGetResult ClusterIdInput::get(const Network::MatchingData& data) const {
23+
// Try to cast to our custom matching data implementation.
24+
const auto* rt_data = dynamic_cast<const ReverseTunnelMatchingDataImpl*>(&data);
25+
if (rt_data != nullptr) {
26+
return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable,
27+
std::string(rt_data->clusterId())};
28+
}
29+
30+
// If we can't get the data, return no data (monostate).
31+
return {Matcher::DataInputGetResult::DataAvailability::AllDataAvailable, absl::monostate()};
32+
}
33+
34+
Matcher::DataInputFactoryCb<Network::MatchingData>
35+
NodeIdInputFactory::createDataInputFactoryCb(const Protobuf::Message&,
36+
ProtobufMessage::ValidationVisitor&) {
37+
return []() { return std::make_unique<NodeIdInput>(); };
38+
}
39+
40+
Matcher::DataInputFactoryCb<Network::MatchingData>
41+
ClusterIdInputFactory::createDataInputFactoryCb(const Protobuf::Message&,
42+
ProtobufMessage::ValidationVisitor&) {
43+
return []() { return std::make_unique<ClusterIdInput>(); };
44+
}
45+
46+
// Register the input factories.
47+
REGISTER_FACTORY(NodeIdInputFactory, Matcher::DataInputFactory<Network::MatchingData>);
48+
REGISTER_FACTORY(ClusterIdInputFactory, Matcher::DataInputFactory<Network::MatchingData>);
49+
50+
} // namespace ReverseTunnel
51+
} // namespace NetworkFilters
52+
} // namespace Extensions
53+
} // namespace Envoy
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
#pragma once
2+
3+
#include "envoy/extensions/filters/network/reverse_tunnel/v3/reverse_tunnel.pb.h"
4+
#include "envoy/matcher/matcher.h"
5+
#include "envoy/server/factory_context.h"
6+
7+
#include "source/common/network/matching/data_impl.h"
8+
9+
namespace Envoy {
10+
namespace Extensions {
11+
namespace NetworkFilters {
12+
namespace ReverseTunnel {
13+
14+
// Custom matching data that extends Network::MatchingDataImpl to provide node_id and cluster_id.
15+
class ReverseTunnelMatchingDataImpl : public Network::Matching::MatchingDataImpl {
16+
public:
17+
ReverseTunnelMatchingDataImpl(absl::string_view node_id, absl::string_view cluster_id,
18+
const Network::ConnectionSocket& socket,
19+
const StreamInfo::FilterState& filter_state,
20+
const envoy::config::core::v3::Metadata& dynamic_metadata)
21+
: Network::Matching::MatchingDataImpl(socket, filter_state, dynamic_metadata),
22+
node_id_(node_id), cluster_id_(cluster_id) {}
23+
24+
absl::string_view nodeId() const { return node_id_; }
25+
absl::string_view clusterId() const { return cluster_id_; }
26+
27+
private:
28+
const std::string node_id_;
29+
const std::string cluster_id_;
30+
};
31+
32+
// Input that provides the node ID value from reverse tunnel request headers.
33+
class NodeIdInput : public Matcher::DataInput<Network::MatchingData> {
34+
public:
35+
Matcher::DataInputGetResult get(const Network::MatchingData& data) const override;
36+
};
37+
38+
// Input that provides the cluster ID value from reverse tunnel request headers.
39+
class ClusterIdInput : public Matcher::DataInput<Network::MatchingData> {
40+
public:
41+
Matcher::DataInputGetResult get(const Network::MatchingData& data) const override;
42+
};
43+
44+
// Factory for NodeIdInput.
45+
class NodeIdInputFactory : public Matcher::DataInputFactory<Network::MatchingData> {
46+
public:
47+
std::string name() const override { return "envoy.matching.inputs.reverse_tunnel_node_id"; }
48+
49+
Matcher::DataInputFactoryCb<Network::MatchingData>
50+
createDataInputFactoryCb(const Protobuf::Message& config,
51+
ProtobufMessage::ValidationVisitor&) override;
52+
53+
ProtobufTypes::MessagePtr createEmptyConfigProto() override {
54+
return std::make_unique<envoy::extensions::filters::network::reverse_tunnel::v3::NodeIdInput>();
55+
}
56+
};
57+
58+
// Factory for ClusterIdInput.
59+
class ClusterIdInputFactory : public Matcher::DataInputFactory<Network::MatchingData> {
60+
public:
61+
std::string name() const override { return "envoy.matching.inputs.reverse_tunnel_cluster_id"; }
62+
63+
Matcher::DataInputFactoryCb<Network::MatchingData>
64+
createDataInputFactoryCb(const Protobuf::Message& config,
65+
ProtobufMessage::ValidationVisitor&) override;
66+
67+
ProtobufTypes::MessagePtr createEmptyConfigProto() override {
68+
return std::make_unique<
69+
envoy::extensions::filters::network::reverse_tunnel::v3::ClusterIdInput>();
70+
}
71+
};
72+
73+
} // namespace ReverseTunnel
74+
} // namespace NetworkFilters
75+
} // namespace Extensions
76+
} // namespace Envoy

0 commit comments

Comments
 (0)