Skip to content
Open
4 changes: 4 additions & 0 deletions docs/root/intro/arch_overview/other_protocols/redis.rst
Original file line number Diff line number Diff line change
Expand Up @@ -215,10 +215,14 @@ For details on each command's usage see the official
SISMEMBER, Set
SMEMBERS, Set
SPOP, Set
SPUBLISH Pubsub
SRANDMEMBER, Set
SREM, Set
SCAN, Generic
SSCAN, Set
SSUBSCRIBE Pubsub
SUBSCRIBE, Pubsub
SUNSUBSCRIBE Pubsub
WATCH, String
UNWATCH, String
ZADD, Sorted Set
Expand Down
1 change: 1 addition & 0 deletions source/common/quic/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ envoy_cc_library(
"//envoy/server:transport_socket_config_interface",
"//envoy/ssl:context_config_interface",
"//source/common/common:assert_lib",
"//source/common/network:raw_buffer_socket_lib",
"//source/common/network:transport_socket_options_lib",
"//source/common/tls:server_context_config_lib",
"//source/common/tls:server_context_lib",
Expand Down
177 changes: 174 additions & 3 deletions source/common/quic/envoy_quic_proof_source.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,18 @@

#include <openssl/bio.h>

#include <cstdlib>
#include <fstream>

#include "envoy/ssl/tls_certificate_config.h"

#include "source/common/quic/cert_compression.h"
#include "source/common/quic/envoy_quic_utils.h"
#include "source/common/quic/quic_io_handle_wrapper.h"
#include "source/common/runtime/runtime_features.h"
#include "source/common/stream_info/stream_info_impl.h"
#include "source/common/tls/context_config_impl.h"
#include "source/common/network/utility.h"

#include "openssl/bytestring.h"
#include "quiche/quic/core/crypto/certificate_view.h"
Expand All @@ -29,7 +34,7 @@ EnvoyQuicProofSource::GetCertChain(const quic::QuicSocketAddress& server_address
return nullptr;
}

return getTlsCertAndFilterChain(*res, hostname, cert_matched_sni).cert_;
return getTlsCertAndFilterChain(*res, hostname, cert_matched_sni, server_address, client_address).cert_;
}

void EnvoyQuicProofSource::signPayload(
Expand All @@ -44,7 +49,7 @@ void EnvoyQuicProofSource::signPayload(
}

CertWithFilterChain res =
getTlsCertAndFilterChain(*data, hostname, nullptr /* cert_matched_sni */);
getTlsCertAndFilterChain(*data, hostname, nullptr /* cert_matched_sni */, server_address, client_address);
if (res.private_key_ == nullptr) {
ENVOY_LOG(warn, "No matching filter chain found for handshake.");
callback->Run(false, "", nullptr);
Expand Down Expand Up @@ -74,13 +79,26 @@ void EnvoyQuicProofSource::signPayload(
EnvoyQuicProofSource::CertWithFilterChain
EnvoyQuicProofSource::getTlsCertAndFilterChain(const TransportSocketFactoryWithFilterChain& data,
const std::string& hostname,
bool* cert_matched_sni) {
bool* cert_matched_sni,
const quic::QuicSocketAddress& server_address,
const quic::QuicSocketAddress& client_address) {
auto [cert, key] =
data.transport_socket_factory_.getTlsCertificateAndKey(hostname, cert_matched_sni);
if (cert == nullptr || key == nullptr) {
ENVOY_LOG(warn, "No certificate is configured in transport socket config.");
return {};
}

// Cache the keylog configuration and connection info for this filter chain
try {
const auto& context_config = data.transport_socket_factory_.getContextConfig();
storeKeylogInfo(data.filter_chain_,
std::shared_ptr<const Ssl::ContextConfig>(&context_config, [](const Ssl::ContextConfig*){}),
server_address, client_address);
} catch (const std::exception& e) {
ENVOY_LOG(debug, "Failed to cache keylog info for filter chain: {}", e.what());
}

return {std::move(cert), std::move(key), data.filter_chain_};
}

Expand Down Expand Up @@ -117,6 +135,159 @@ void EnvoyQuicProofSource::updateFilterChainManager(

void EnvoyQuicProofSource::OnNewSslCtx(SSL_CTX* ssl_ctx) {
CertCompression::registerSslContext(ssl_ctx);

// Try to set up keylog callback for QUIC SSL contexts
setupQuicKeylogCallback(ssl_ctx);
}

void EnvoyQuicProofSource::setupQuicKeylogCallback(SSL_CTX* ssl_ctx) {
// Store reference to this proof source in SSL_CTX for use in keylog callback
SSL_CTX_set_app_data(ssl_ctx, this);

// Set up the keylog callback - the actual keylog configuration will be
// determined per-connection in the callback based on the filter chain
SSL_CTX_set_keylog_callback(ssl_ctx, quicKeylogCallback);
}

// Helper function to convert Envoy address to QUICHE address
quic::QuicSocketAddress envoyAddressToQuicAddress(const Network::Address::Instance& envoy_addr) {
if (envoy_addr.type() == Network::Address::Type::Ip) {
const auto& ip_addr = *envoy_addr.ip();
quiche::QuicheIpAddress quiche_addr;
if (quiche_addr.FromString(ip_addr.addressAsString())) {
return quic::QuicSocketAddress(quic::QuicIpAddress(quiche_addr), ip_addr.port());
}
}
// Return any address for non-IP addresses
return quic::QuicSocketAddress();
}

// Static keylog callback for QUIC SSL contexts
void EnvoyQuicProofSource::quicKeylogCallback(const SSL* ssl, const char* line) {
ASSERT(ssl != nullptr);

// Get the proof source instance from SSL_CTX
auto* proof_source =
static_cast<EnvoyQuicProofSource*>(SSL_CTX_get_app_data(SSL_get_SSL_CTX(ssl)));
ASSERT(proof_source != nullptr);

ENVOY_LOG(debug, "QUIC keylog callback invoked for line: {}", line);

// Try to find keylog configuration from cached filter chain information
// We iterate through all cached filter chains to find one with keylog configuration
bool keylog_written = false;
{
absl::MutexLock lock(&proof_source->keylog_cache_mutex_);
for (const auto& entry : proof_source->keylog_config_cache_) {
const auto& keylog_info = entry.second;
if (keylog_info.config) {
try {
// Convert QUIC addresses back to Envoy addresses for the bridge
std::string server_addr_str = absl::StrCat(
keylog_info.server_address.host().ToString(), ":",
keylog_info.server_address.port());
std::string client_addr_str = absl::StrCat(
keylog_info.client_address.host().ToString(), ":",
keylog_info.client_address.port());

Network::Address::InstanceConstSharedPtr local_addr =
Network::Utility::parseInternetAddressAndPortNoThrow(server_addr_str);
Network::Address::InstanceConstSharedPtr remote_addr =
Network::Utility::parseInternetAddressAndPortNoThrow(client_addr_str);

if (local_addr && remote_addr) {
QuicKeylogBridge::writeKeylog(*keylog_info.config, *local_addr, *remote_addr, line);
keylog_written = true;
ENVOY_LOG(debug, "QUIC keylog written using cached configuration");
break; // Successfully handled by built-in system
}
} catch (const std::exception& e) {
ENVOY_LOG(debug, "Failed to write keylog using cached config: {}", e.what());
}
}
}
}

if (keylog_written) {
return;
}

// Fallback: Use environment variable for backward compatibility
const char* keylog_path = std::getenv("SSLKEYLOGFILE");
if (keylog_path != nullptr) {
std::ofstream keylog_file(keylog_path, std::ios::app);
if (keylog_file.is_open()) {
keylog_file << line << "\n";
keylog_file.close();
ENVOY_LOG(debug, "QUIC keylog written to {}: {}", keylog_path, line);
}
}
}

void EnvoyQuicProofSource::QuicKeylogBridge::writeKeylog(
const Ssl::ContextConfig& config,
const Network::Address::Instance& local_addr,
const Network::Address::Instance& remote_addr,
const char* line) {

const std::string& keylog_path = config.tlsKeyLogPath();
if (keylog_path.empty()) {
return;
}

// Check address filtering
const auto& local_ip_list = config.tlsKeyLogLocal();
const auto& remote_ip_list = config.tlsKeyLogRemote();

bool local_match = (local_ip_list.getIpListSize() == 0 || local_ip_list.contains(local_addr));
bool remote_match = (remote_ip_list.getIpListSize() == 0 || remote_ip_list.contains(remote_addr));

if (!local_match || !remote_match) {
ENVOY_LOG(debug, "QUIC keylog filtered out by address match (local={}, remote={})",
local_match, remote_match);
return;
}

// Use access log manager to write keylog
try {
auto& access_log_manager = config.accessLogManager();
auto file_or_error = access_log_manager.createAccessLog(
Filesystem::FilePathAndType{Filesystem::DestinationType::File, keylog_path});

if (file_or_error.ok()) {
auto keylog_file = file_or_error.value();
keylog_file->write(absl::StrCat(line, "\n"));
ENVOY_LOG(debug, "QUIC keylog written via bridge to {}: {}", keylog_path, line);
} else {
ENVOY_LOG(warn, "Failed to create keylog file {}: {}", keylog_path,
file_or_error.status().message());
}
} catch (const std::exception& e) {
ENVOY_LOG(warn, "Failed to write QUIC keylog: {}", e.what());
}
}

// Get SSL socket index for storing transport socket callbacks
int EnvoyQuicProofSource::sslSocketIndex() {
static int ssl_socket_index = SSL_get_ex_new_index(0, nullptr, nullptr, nullptr, nullptr);
return ssl_socket_index;
}

void EnvoyQuicProofSource::storeKeylogInfo(const Network::FilterChain& filter_chain,
std::shared_ptr<const Ssl::ContextConfig> config,
const quic::QuicSocketAddress& server_address,
const quic::QuicSocketAddress& client_address) const {
absl::MutexLock lock(&keylog_cache_mutex_);
keylog_config_cache_[&filter_chain] = KeylogInfo{std::move(config), server_address, client_address};
}

absl::optional<EnvoyQuicProofSource::KeylogInfo> EnvoyQuicProofSource::getKeylogInfo(const Network::FilterChain& filter_chain) const {
absl::MutexLock lock(&keylog_cache_mutex_);
auto it = keylog_config_cache_.find(&filter_chain);
if (it != keylog_config_cache_.end()) {
return it->second;
}
return absl::nullopt;
}

} // namespace Quic
Expand Down
48 changes: 47 additions & 1 deletion source/common/quic/envoy_quic_proof_source.h
Original file line number Diff line number Diff line change
@@ -1,15 +1,30 @@
#pragma once

#include <unordered_map>

#include "envoy/ssl/context_config.h"

#include "source/common/common/thread.h"
#include "source/common/quic/envoy_quic_proof_source_base.h"
#include "source/common/quic/quic_server_transport_socket_factory.h"
#include "source/server/listener_stats.h"

#include "absl/synchronization/mutex.h"
#include "absl/types/optional.h"
#include "quiche/quic/platform/api/quic_socket_address.h"

namespace Envoy {
namespace Quic {

// A ProofSource implementation which supplies a proof instance with certs from filter chain.
class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase {
public:
// Cache for keylog configurations by filter chain
struct KeylogInfo {
std::shared_ptr<const Ssl::ContextConfig> config;
quic::QuicSocketAddress server_address;
quic::QuicSocketAddress client_address;
};
EnvoyQuicProofSource(Network::Socket& listen_socket,
Network::FilterChainManager& filter_chain_manager,
Server::ListenerStats& listener_stats, TimeSource& time_source)
Expand All @@ -27,6 +42,15 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase {

void updateFilterChainManager(Network::FilterChainManager& filter_chain_manager);

// Bridge interface for QUIC-TLS keylog integration
class QuicKeylogBridge {
public:
static void writeKeylog(const Ssl::ContextConfig& config,
const Network::Address::Instance& local_addr,
const Network::Address::Instance& remote_addr,
const char* line);
};

protected:
// quic::ProofSource
void signPayload(const quic::QuicSocketAddress& server_address,
Expand All @@ -47,17 +71,39 @@ class EnvoyQuicProofSource : public EnvoyQuicProofSourceBase {
};

CertWithFilterChain getTlsCertAndFilterChain(const TransportSocketFactoryWithFilterChain& data,
const std::string& hostname, bool* cert_matched_sni);
const std::string& hostname, bool* cert_matched_sni,
const quic::QuicSocketAddress& server_address,
const quic::QuicSocketAddress& client_address);

absl::optional<TransportSocketFactoryWithFilterChain>
getTransportSocketAndFilterChain(const quic::QuicSocketAddress& server_address,
const quic::QuicSocketAddress& client_address,
const std::string& hostname);

void setupQuicKeylogCallback(SSL_CTX* ssl_ctx);

// Static callback function for QUIC keylog
static void quicKeylogCallback(const SSL* ssl, const char* line);

// Get SSL socket index for storing transport socket callbacks
static int sslSocketIndex();

// Store keylog configuration and connection info for a filter chain
void storeKeylogInfo(const Network::FilterChain& filter_chain,
std::shared_ptr<const Ssl::ContextConfig> config,
const quic::QuicSocketAddress& server_address,
const quic::QuicSocketAddress& client_address) const;

// Get cached keylog information for a filter chain
absl::optional<KeylogInfo> getKeylogInfo(const Network::FilterChain& filter_chain) const;

Network::Socket& listen_socket_;
Network::FilterChainManager* filter_chain_manager_{nullptr};
Server::ListenerStats& listener_stats_;
TimeSource& time_source_;

mutable absl::Mutex keylog_cache_mutex_;
mutable std::unordered_map<const Network::FilterChain*, KeylogInfo> keylog_config_cache_ ABSL_GUARDED_BY(keylog_cache_mutex_);
};

} // namespace Quic
Expand Down
10 changes: 9 additions & 1 deletion source/common/quic/quic_server_transport_socket_factory.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "envoy/ssl/handshaker.h"

#include "source/common/common/assert.h"
#include "source/common/network/raw_buffer_socket.h"
#include "source/common/network/transport_socket_options_impl.h"
#include "source/common/quic/quic_transport_socket_factory.h"
#include "source/common/tls/server_ssl_socket.h"
Expand All @@ -25,8 +26,12 @@ class QuicServerTransportSocketFactory : public Network::DownstreamTransportSock
~QuicServerTransportSocketFactory() override;

// Network::DownstreamTransportSocketFactory
// QUIC uses a different transport socket mechanism, but some code paths may call this
// Return a raw buffer socket as a safe fallback
Network::TransportSocketPtr createDownstreamTransportSocket() const override {
PANIC("not implemented");
ENVOY_LOG(warn, "createDownstreamTransportSocket called on QUIC transport socket factory. "
"This should not happen in normal QUIC operation.");
return std::make_unique<Network::RawBufferSocket>();
}
bool implementsSecureTransport() const override { return true; }

Expand All @@ -38,6 +43,9 @@ class QuicServerTransportSocketFactory : public Network::DownstreamTransportSock

bool earlyDataEnabled() const { return enable_early_data_; }

// Access the TLS context configuration (for keylog integration)
const Ssl::ServerContextConfig& getContextConfig() const { return *config_; }

protected:
QuicServerTransportSocketFactory(bool enable_early_data, Stats::Scope& store,
Ssl::ServerContextConfigPtr config,
Expand Down
Loading