Skip to content

Commit baca791

Browse files
authored
Add dynamic metadata for TLS inspector filter errors (#40529)
Export dynamic metadata from the TLS inspector to provide more detailed reason for subsequent extensions in case SNI could not be detected. Signed-off-by: Elisha Ziskind <[email protected]>
1 parent b553edc commit baca791

File tree

7 files changed

+89
-1
lines changed

7 files changed

+89
-1
lines changed

changelogs/current.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,9 @@ new_features:
318318
- area: rbac
319319
change: |
320320
Switch the IP matcher to use LC-Trie for performance improvements.
321+
- area: tls_inspector
322+
change: |
323+
Added dynamic metadata when failing to parse the ``ClientHello``.
321324
- area: lua
322325
change: |
323326
Added ``route()`` to the Stream handle API, allowing Lua scripts to retrieve route information. So far, the only method

docs/root/configuration/advanced/well_known_dynamic_metadata.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ The following Envoy filters can be configured to consume dynamic metadata emitte
3232
<envoy_v3_api_field_extensions.filters.http.ext_authz.v3.ExtAuthz.metadata_context_namespaces>`
3333
* :ref:`RateLimit Filter limit override <config_http_filters_rate_limit_override_dynamic_metadata>`
3434
* :ref:`Original destination listener filter <config_listener_filters_original_dst>`
35+
* :ref:`TLS Inspector listener filter <config_listener_filters_tls_inspector_dynamic_metadata>`
3536

3637
.. _shared_dynamic_metadata:
3738

docs/root/configuration/listeners/listener_filters/tls_inspector.rst

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,4 +81,12 @@ This filter has a statistics tree rooted at *tls_inspector* with the following s
8181
If the connection terminates early nothing is recorded if we didn't have
8282
sufficient bytes for either of the cases above.
8383

84+
.. _config_listener_filters_tls_inspector_dynamic_metadata:
85+
86+
Dynamic Metadata
87+
----------------
88+
89+
If the filter fails to detect TLS it will populate dynamic metadata under the key
90+
`envoy.filters.listener.tls_inspector` indicating the reason (eg. ``ClientHello`` too
91+
large or not detected at all).
8492

source/extensions/filters/listener/tls_inspector/tls_inspector.cc

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,13 @@ Network::FilterStatus Filter::onData(Network::ListenerFilterBuffer& buffer) {
175175
return Network::FilterStatus::StopIteration;
176176
}
177177

178+
void Filter::setDynamicMetadata(absl::string_view failure_reason) {
179+
Protobuf::Struct metadata;
180+
auto& fields = *metadata.mutable_fields();
181+
fields[failureReasonKey()].set_string_value(failure_reason);
182+
cb_->setDynamicMetadata(dynamicMetadataKey(), metadata);
183+
}
184+
178185
ParseState Filter::parseClientHello(const void* data, size_t len,
179186
uint64_t bytes_already_processed) {
180187
// Ownership remains here though we pass a reference to it in `SSL_set0_rbio()`.
@@ -198,6 +205,7 @@ ParseState Filter::parseClientHello(const void* data, size_t len,
198205
// We've hit the specified size limit. This is an unreasonably large ClientHello;
199206
// indicate failure.
200207
config_->stats().client_hello_too_large_.inc();
208+
setDynamicMetadata(failureReasonClientHelloTooLarge());
201209
return ParseState::Error;
202210
}
203211
if (read_ >= requested_read_bytes_) {
@@ -219,9 +227,11 @@ ParseState Filter::parseClientHello(const void* data, size_t len,
219227
// We've hit the specified size limit. This is an unreasonably large ClientHello;
220228
// indicate failure.
221229
config_->stats().client_hello_too_large_.inc();
230+
setDynamicMetadata(failureReasonClientHelloTooLarge());
222231
return ParseState::Error;
223232
}
224233
config_->stats().tls_not_found_.inc();
234+
setDynamicMetadata(failureReasonClientHelloNotDetected());
225235
ENVOY_LOG(
226236
debug, "tls inspector: parseClientHello failed: {}",
227237
Extensions::TransportSockets::Tls::Utility::getLastCryptoError().value_or("unknown"));
@@ -369,6 +379,22 @@ void Filter::createJA4Hash(const SSL_CLIENT_HELLO* ssl_client_hello) {
369379
cb_->socket().setJA4Hash(fingerprint);
370380
}
371381

382+
const std::string& Filter::dynamicMetadataKey() {
383+
CONSTRUCT_ON_FIRST_USE(std::string, "envoy.filters.listener.tls_inspector");
384+
}
385+
386+
const std::string& Filter::failureReasonKey() {
387+
CONSTRUCT_ON_FIRST_USE(std::string, "failure_reason");
388+
}
389+
390+
const std::string& Filter::failureReasonClientHelloTooLarge() {
391+
CONSTRUCT_ON_FIRST_USE(std::string, "ClientHelloTooLarge");
392+
}
393+
394+
const std::string& Filter::failureReasonClientHelloNotDetected() {
395+
CONSTRUCT_ON_FIRST_USE(std::string, "ClientHelloNotDetected");
396+
}
397+
372398
} // namespace TlsInspector
373399
} // namespace ListenerFilters
374400
} // namespace Extensions

source/extensions/filters/listener/tls_inspector/tls_inspector.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ enum class ParseState {
4747
// Parser reports unrecoverable error.
4848
Error
4949
};
50+
5051
/**
5152
* Global configuration for TLS inspector.
5253
*/
@@ -93,6 +94,11 @@ class Filter : public Network::ListenerFilter, Logger::Loggable<Logger::Id::filt
9394
Network::FilterStatus onData(Network::ListenerFilterBuffer& buffer) override;
9495
size_t maxReadBytes() const override { return requested_read_bytes_; }
9596

97+
static const std::string& dynamicMetadataKey();
98+
static const std::string& failureReasonKey();
99+
static const std::string& failureReasonClientHelloTooLarge();
100+
static const std::string& failureReasonClientHelloNotDetected();
101+
96102
private:
97103
ParseState parseClientHello(const void* data, size_t len, uint64_t bytes_already_processed);
98104
ParseState onRead();
@@ -101,6 +107,7 @@ class Filter : public Network::ListenerFilter, Logger::Loggable<Logger::Id::filt
101107
void createJA3Hash(const SSL_CLIENT_HELLO* ssl_client_hello);
102108
void createJA4Hash(const SSL_CLIENT_HELLO* ssl_client_hello);
103109
uint32_t maxConfigReadBytes() const { return config_->maxClientHelloSize(); }
110+
void setDynamicMetadata(absl::string_view failure_reason);
104111

105112
ConfigSharedPtr config_;
106113
Network::ListenerFilterCallbacks* cb_{};

test/extensions/filters/listener/tls_inspector/tls_inspector_integration_test.cc

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,36 @@ TEST_P(TlsInspectorIntegrationTest, ContinueOnListenerTimeout) {
260260
EXPECT_THAT(waitForAccessLog(listener_access_log_name_), testing::Eq("-"));
261261
}
262262

263+
TEST_P(TlsInspectorIntegrationTest, TlsInspectorMetadataPopulatedInAccessLog) {
264+
initializeWithTlsInspector(
265+
/*ssl_client=*/false,
266+
/*log_format=*/"%DYNAMIC_METADATA(envoy.filters.listener.tls_inspector:failure_reason)%",
267+
false, false, false);
268+
Network::Address::InstanceConstSharedPtr address =
269+
Ssl::getSslAddress(version_, lookupPort("echo"));
270+
context_ =
271+
Ssl::createClientSslTransportSocketFactory(/*ssl_options=*/{}, *context_manager_, *api_);
272+
auto transport_socket_factory = std::make_unique<Network::RawBufferSocketFactory>();
273+
Network::TransportSocketPtr transport_socket =
274+
transport_socket_factory->createTransportSocket(nullptr, nullptr);
275+
client_ = dispatcher_->createClientConnection(address, Network::Address::InstanceConstSharedPtr(),
276+
std::move(transport_socket), nullptr, nullptr);
277+
std::shared_ptr<WaitForPayloadReader> payload_reader =
278+
std::make_shared<WaitForPayloadReader>(*dispatcher_);
279+
client_->addReadFilter(payload_reader);
280+
client_->addConnectionCallbacks(connect_callbacks_);
281+
client_->connect();
282+
Buffer::OwnedImpl buffer("fake data");
283+
client_->write(buffer, false);
284+
while (!connect_callbacks_.connected() && !connect_callbacks_.closed()) {
285+
dispatcher_->run(Event::Dispatcher::RunType::NonBlock);
286+
}
287+
// The timeout is set as one seconds, advance 2 seconds to trigger the timeout.
288+
timeSystem().advanceTimeWaitImpl(std::chrono::milliseconds(2000));
289+
client_->close(Network::ConnectionCloseType::NoFlush);
290+
EXPECT_THAT(waitForAccessLog(listener_access_log_name_), testing::Eq("ClientHelloNotDetected"));
291+
}
292+
263293
// The `JA3` fingerprint is correct in the access log.
264294
TEST_P(TlsInspectorIntegrationTest, JA3FingerprintIsSet) {
265295
// These TLS options will create a client hello message with

test/extensions/filters/listener/tls_inspector/tls_inspector_test.cc

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class TlsInspectorTest : public testing::TestWithParam<std::tuple<uint16_t, uint
103103
Stats::TestUtil::TestStore store_;
104104
ConfigSharedPtr cfg_;
105105
std::unique_ptr<Filter> filter_;
106-
Network::MockListenerFilterCallbacks cb_;
106+
NiceMock<Network::MockListenerFilterCallbacks> cb_;
107107
Network::MockConnectionSocket socket_;
108108
NiceMock<Event::MockDispatcher> dispatcher_;
109109
Event::FileReadyCb file_event_callback_;
@@ -281,6 +281,12 @@ TEST_P(TlsInspectorTest, ClientHelloTooBig) {
281281
mockSysCallForPeek(client_hello, true);
282282
EXPECT_CALL(socket_, detectedTransportProtocol()).Times(::testing::AnyNumber());
283283
EXPECT_TRUE(file_event_callback_(Event::FileReadyType::Read).ok());
284+
285+
Protobuf::Struct expected_metadata;
286+
auto& fields = *expected_metadata.mutable_fields();
287+
fields[Filter::failureReasonKey()].set_string_value(Filter::failureReasonClientHelloTooLarge());
288+
EXPECT_CALL(cb_, setDynamicMetadata(Filter::dynamicMetadataKey(), ProtoEq(expected_metadata)));
289+
284290
auto state = filter_->onData(*buffer_);
285291
EXPECT_EQ(Network::FilterStatus::StopIteration, state);
286292
EXPECT_EQ(1, cfg_->stats().client_hello_too_large_.value());
@@ -409,6 +415,13 @@ TEST_P(TlsInspectorTest, NotSsl) {
409415
mockSysCallForPeek(data);
410416
// trigger the event to copy the client hello message into buffer:q
411417
EXPECT_TRUE(file_event_callback_(Event::FileReadyType::Read).ok());
418+
419+
Protobuf::Struct expected_metadata;
420+
auto& fields = *expected_metadata.mutable_fields();
421+
fields[Filter::failureReasonKey()].set_string_value(
422+
Filter::failureReasonClientHelloNotDetected());
423+
EXPECT_CALL(cb_, setDynamicMetadata(Filter::dynamicMetadataKey(), ProtoEq(expected_metadata)));
424+
412425
auto state = filter_->onData(*buffer_);
413426
EXPECT_EQ(Network::FilterStatus::Continue, state);
414427
EXPECT_EQ(1, cfg_->stats().tls_not_found_.value());

0 commit comments

Comments
 (0)