From 0c471003541e0835a6f190104e5214da4ca9a026 Mon Sep 17 00:00:00 2001 From: Masakazu Kitajo Date: Tue, 7 Nov 2023 08:21:36 +0900 Subject: [PATCH] Cleanup P_SNIActionPerformer (#10727) Move function definitions into the .cc file so the hedaer file doesn't bring unrelated dependencies. --- .../{SNIActionPerformer.h => SNIActionItem.h} | 3 +- include/iocore/net/SSLSNIConfig.h | 2 +- include/iocore/net/YamlSNIConfig.h | 3 +- src/iocore/net/P_Connection.h | 1 + src/iocore/net/P_SNIActionPerformer.h | 582 ------------------ src/iocore/net/SNIActionPerformer.cc | 321 +++++++++- src/iocore/net/SNIActionPerformer.h | 304 +++++++++ src/iocore/net/SSLSNIConfig.cc | 5 +- src/iocore/net/YamlSNIConfig.cc | 4 +- src/proxy/http/HttpSM.cc | 1 + src/proxy/http3/Http3SessionAccept.cc | 1 + 11 files changed, 639 insertions(+), 588 deletions(-) rename include/iocore/net/{SNIActionPerformer.h => SNIActionItem.h} (98%) delete mode 100644 src/iocore/net/P_SNIActionPerformer.h create mode 100644 src/iocore/net/SNIActionPerformer.h diff --git a/include/iocore/net/SNIActionPerformer.h b/include/iocore/net/SNIActionItem.h similarity index 98% rename from include/iocore/net/SNIActionPerformer.h rename to include/iocore/net/SNIActionItem.h index 5e498228574..11902f3481b 100644 --- a/include/iocore/net/SNIActionPerformer.h +++ b/include/iocore/net/SNIActionItem.h @@ -32,7 +32,8 @@ #include #include -#include "iocore/net/TLSSNISupport.h" +#include + #include "tscore/ink_inet.h" #include "ts/DbgCtl.h" diff --git a/include/iocore/net/SSLSNIConfig.h b/include/iocore/net/SSLSNIConfig.h index 434d43d2025..856c1b9f301 100644 --- a/include/iocore/net/SSLSNIConfig.h +++ b/include/iocore/net/SSLSNIConfig.h @@ -40,7 +40,7 @@ #include "tscpp/util/ts_ip.h" #include "iocore/eventsystem/ConfigProcessor.h" -#include "iocore/net/SNIActionPerformer.h" +#include "iocore/net/SNIActionItem.h" #include "iocore/net/YamlSNIConfig.h" #include diff --git a/include/iocore/net/YamlSNIConfig.h b/include/iocore/net/YamlSNIConfig.h index eed19f56bee..ce57136e795 100644 --- a/include/iocore/net/YamlSNIConfig.h +++ b/include/iocore/net/YamlSNIConfig.h @@ -29,7 +29,6 @@ #include #include -#include "iocore/net/SNIActionPerformer.h" #include "iocore/net/SSLTypes.h" #include "tscpp/util/ts_ip.h" @@ -74,6 +73,8 @@ TSDECL(http2_initial_window_size_in); TSDECL(server_max_early_data); #undef TSDECL +class ActionItem; + struct YamlSNIConfig { enum class Policy : uint8_t { DISABLED = 0, PERMISSIVE, ENFORCED, UNSET }; enum class Property : uint8_t { NONE = 0, SIGNATURE_MASK = 0x1, NAME_MASK = 0x2, ALL_MASK = 0x3, UNSET }; diff --git a/src/iocore/net/P_Connection.h b/src/iocore/net/P_Connection.h index dd1a95c051c..ffbe51d9b90 100644 --- a/src/iocore/net/P_Connection.h +++ b/src/iocore/net/P_Connection.h @@ -50,6 +50,7 @@ #pragma once #include "tscore/ink_platform.h" +#include "iocore/net/NetProcessor.h" struct NetVCOptions; diff --git a/src/iocore/net/P_SNIActionPerformer.h b/src/iocore/net/P_SNIActionPerformer.h deleted file mode 100644 index 9f5fc67871d..00000000000 --- a/src/iocore/net/P_SNIActionPerformer.h +++ /dev/null @@ -1,582 +0,0 @@ -/** @file - - A brief file description - - @section license License - - Licensed to the Apache Software Foundation (ASF) under one - or more contributor license agreements. See the NOTICE file - distributed with this work for additional information - regarding copyright ownership. The ASF licenses this file - to you under the Apache License, Version 2.0 (the - "License"); you may not use this file except in compliance - with the License. You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. - */ - -/*************************** -*- Mod: C++ -*- ****************************** - P_ActionProcessor.h - Created On : 05/02/2017 - - Description: - SNI based Configuration in ATS - ****************************************************************************/ -#pragma once - -#include "swoc/TextView.h" -#include "swoc/swoc_ip.h" - -#include "iocore/eventsystem/EventSystem.h" -#include "P_SSLNextProtocolAccept.h" -#include "P_SSLNetVConnection.h" -#include "iocore/net/SNIActionPerformer.h" -#include "iocore/net/SSLTypes.h" - -#include "tscore/ink_inet.h" - -#include - -class ControlQUIC : public ActionItem -{ -public: -#if TS_USE_QUIC == 1 - ControlQUIC(bool turn_on) : enable_quic(turn_on) {} -#else - ControlQUIC(bool turn_on) {} -#endif - ~ControlQUIC() override {} - - int SNIAction(SSL &ssl, const Context &ctx) const override; - -private: -#if TS_USE_QUIC == 1 - bool enable_quic = false; -#endif -}; - -class ControlH2 : public ActionItem -{ -public: - ControlH2(bool turn_on) : enable_h2(turn_on) {} - ~ControlH2() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - auto snis = TLSSNISupport::getInstance(&ssl); - auto alpns = ALPNSupport::getInstance(&ssl); - - if (snis == nullptr || alpns == nullptr) { - return SSL_TLSEXT_ERR_OK; - } - - const char *servername = snis->get_sni_server_name(); - if (!enable_h2) { - alpns->disableProtocol(TS_ALPN_PROTOCOL_INDEX_HTTP_2_0); - Dbg(dbg_ctl_ssl_sni, "H2 disabled, fqdn [%s]", servername); - } else { - alpns->enableProtocol(TS_ALPN_PROTOCOL_INDEX_HTTP_2_0); - Dbg(dbg_ctl_ssl_sni, "H2 enabled, fqdn [%s]", servername); - } - return SSL_TLSEXT_ERR_OK; - } - -private: - bool enable_h2 = false; -}; - -class HTTP2BufferWaterMark : public ActionItem -{ -public: - HTTP2BufferWaterMark(int value) : value(value) {} - ~HTTP2BufferWaterMark() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - if (auto snis = TLSSNISupport::getInstance(&ssl)) { - snis->hints_from_sni.http2_buffer_water_mark = value; - } - return SSL_TLSEXT_ERR_OK; - } - -private: - int value = -1; -}; - -class HTTP2InitialWindowSizeIn : public ActionItem -{ -public: - HTTP2InitialWindowSizeIn(int value) : value(value) {} - ~HTTP2InitialWindowSizeIn() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - if (auto snis = TLSSNISupport::getInstance(&ssl)) { - snis->hints_from_sni.http2_initial_window_size_in = value; - } - return SSL_TLSEXT_ERR_OK; - } - -private: - int value = -1; -}; - -class HTTP2MaxSettingsFramesPerMinute : public ActionItem -{ -public: - HTTP2MaxSettingsFramesPerMinute(int value) : value(value) {} - ~HTTP2MaxSettingsFramesPerMinute() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - if (auto snis = TLSSNISupport::getInstance(&ssl)) { - snis->hints_from_sni.http2_max_settings_frames_per_minute = value; - } - return SSL_TLSEXT_ERR_OK; - } - -private: - int value = -1; -}; - -class HTTP2MaxPingFramesPerMinute : public ActionItem -{ -public: - HTTP2MaxPingFramesPerMinute(int value) : value(value) {} - ~HTTP2MaxPingFramesPerMinute() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - if (auto snis = TLSSNISupport::getInstance(&ssl)) { - snis->hints_from_sni.http2_max_ping_frames_per_minute = value; - } - return SSL_TLSEXT_ERR_OK; - } - -private: - int value = -1; -}; - -class HTTP2MaxPriorityFramesPerMinute : public ActionItem -{ -public: - HTTP2MaxPriorityFramesPerMinute(int value) : value(value) {} - ~HTTP2MaxPriorityFramesPerMinute() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - if (auto snis = TLSSNISupport::getInstance(&ssl)) { - snis->hints_from_sni.http2_max_priority_frames_per_minute = value; - } - return SSL_TLSEXT_ERR_OK; - } - -private: - int value = -1; -}; - -class HTTP2MaxRstStreamFramesPerMinute : public ActionItem -{ -public: - HTTP2MaxRstStreamFramesPerMinute(int value) : value(value) {} - ~HTTP2MaxRstStreamFramesPerMinute() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - if (auto snis = TLSSNISupport::getInstance(&ssl)) { - snis->hints_from_sni.http2_max_rst_stream_frames_per_minute = value; - } - return SSL_TLSEXT_ERR_OK; - } - -private: - int value = -1; -}; - -class TunnelDestination : public ActionItem -{ - // ID of the configured variable. This will be used to know which function - // should be called when processing the tunnel destination. - enum OpId : int32_t { - MATCH_GROUPS, // Deal with configured groups. - MAP_WITH_RECV_PORT, // Use port from inbound local - MAP_WITH_PROXY_PROTOCOL_PORT, // Use port from the proxy protocol - MAX // Always at the end and do not change the value of the above items. - }; - static constexpr std::string_view MAP_WITH_RECV_PORT_STR = "{inbound_local_port}"; - static constexpr std::string_view MAP_WITH_PROXY_PROTOCOL_PORT_STR = "{proxy_protocol_port}"; - -public: - TunnelDestination(const std::string_view &dest, SNIRoutingType type, YamlSNIConfig::TunnelPreWarm prewarm, - const std::vector &alpn) - : destination(dest), type(type), tunnel_prewarm(prewarm), alpn_ids(alpn) - { - // Check for port variable specification. Note that this is checked before - // the match group so that the corresponding function can be applied before - // the match group expansion(when the var_start_pos is still accurate). - auto recv_port_start_pos = destination.find(MAP_WITH_RECV_PORT_STR); - auto pp_port_start_pos = destination.find(MAP_WITH_PROXY_PROTOCOL_PORT_STR); - bool has_recv_port_var = recv_port_start_pos != std::string::npos; - bool has_pp_port_var = pp_port_start_pos != std::string::npos; - if (has_recv_port_var && has_pp_port_var) { - Error("Invalid destination \"%.*s\" in SNI configuration - Only one port variable can be specified.", - static_cast(destination.size()), destination.data()); - } else if (has_recv_port_var) { - fnArrIndexes.push_back(OpId::MAP_WITH_RECV_PORT); - var_start_pos = recv_port_start_pos; - } else if (has_pp_port_var) { - fnArrIndexes.push_back(OpId::MAP_WITH_PROXY_PROTOCOL_PORT); - var_start_pos = pp_port_start_pos; - } - // Check for match groups as well. - if (destination.find_first_of('$') != std::string::npos) { - fnArrIndexes.push_back(OpId::MATCH_GROUPS); - } - } - ~TunnelDestination() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - auto snis = TLSSNISupport::getInstance(&ssl); - auto tuns = TLSTunnelSupport::getInstance(&ssl); - auto alpns = ALPNSupport::getInstance(&ssl); - auto ssl_netvc = SSLNetVCAccess(&ssl); - - if (snis == nullptr || tuns == nullptr || alpns == nullptr || ssl_netvc == nullptr) { - return SSL_TLSEXT_ERR_OK; - } - - const char *servername = snis->get_sni_server_name(); - if (fnArrIndexes.empty()) { - tuns->set_tunnel_destination(destination, type, !TLSTunnelSupport::PORT_IS_DYNAMIC, tunnel_prewarm); - Debug("ssl_sni", "Destination now is [%s], fqdn [%s]", destination.c_str(), servername); - } else { - bool port_is_dynamic = false; - auto fixed_dst{destination}; - // Apply mapping functions to get the final destination. - for (auto fnArrIndex : fnArrIndexes) { - // Dispatch to the correct tunnel destination port function. - fixed_dst = fix_destination[fnArrIndex](fixed_dst, var_start_pos, ctx, ssl_netvc, port_is_dynamic); - } - tuns->set_tunnel_destination(fixed_dst, type, port_is_dynamic, tunnel_prewarm); - Debug("ssl_sni", "Destination now is [%s], configured [%s], fqdn [%s]", fixed_dst.c_str(), destination.c_str(), servername); - } - - if (type == SNIRoutingType::BLIND) { - ssl_netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; - } - - // ALPN - for (int id : alpn_ids) { - alpns->enableProtocol(id); - } - - return SSL_TLSEXT_ERR_OK; - } - -private: - static bool - is_number(std::string_view s) - { - return !s.empty() && - std::find_if(std::begin(s), std::end(s), [](std::string_view::value_type c) { return !std::isdigit(c); }) == std::end(s); - } - - /** - * `tunnel_route` may contain matching groups ie: `$1` which needs to be replaced by the corresponding - * captured group from the `fqdn`, this function will replace them using proper group string. Matching - * groups could be at any order. - */ - static std::string - replace_match_groups(std::string_view dst, const ActionItem::Context::CapturedGroupViewVec &groups, bool &port_is_dynamic) - { - port_is_dynamic = false; - if (dst.empty() || groups.empty()) { - return std::string{dst}; - } - std::string real_dst; - std::string::size_type pos{0}; - - const auto end = std::end(dst); - // We need to split the tunnel string and place each corresponding match on the - // configured one, so we need to first, get the match, then get the match number - // making sure that it does exist in the captured group. - bool is_writing_port = false; - for (auto c = std::begin(dst); c != end; c++, pos++) { - if (*c == ':') { - is_writing_port = true; - } - if (*c == '$') { - // find the next '.' so we can get the group number. - const auto dot = dst.find('.', pos); - std::string::size_type to = std::string::npos; - if (dot != std::string::npos) { - to = dot - (pos + 1); - } else { - // It may not have a dot, which could be because it's the last part. In that case - // we should check for the port separator. - if (const auto port = dst.find(':', pos); port != std::string::npos) { - to = (port - pos) - 1; - } - } - std::string_view number_str{dst.substr(pos + 1, to)}; - if (!is_number(number_str)) { - // it may be some issue on the configured string, place the char and keep going. - real_dst += *c; - continue; - } - const std::size_t group_index = swoc::svtoi(number_str); - if ((group_index - 1) < groups.size()) { - // place the captured group. - real_dst += groups[group_index - 1]; - if (is_writing_port) { - port_is_dynamic = true; - } - // if it was the last match, then ... - if (dot == std::string::npos && to == std::string::npos) { - // that's it. - break; - } - pos += number_str.size() + 1; - std::advance(c, number_str.size() + 1); - } - // If there is no match for a specific group, then we keep the `$#` as defined in the string. - } - real_dst += *c; - } - - return real_dst; - } - - std::string destination; - - /// The start position of a tunnel destination variable, such as '{proxy_protocol_port}'. - size_t var_start_pos{0}; - SNIRoutingType type = SNIRoutingType::NONE; - YamlSNIConfig::TunnelPreWarm tunnel_prewarm = YamlSNIConfig::TunnelPreWarm::UNSET; - const std::vector &alpn_ids; - - /** The indexes of the mapping functions that need to be called. On - creation, we decide which functions need to be called, add the coressponding - indexes and then we call those functions with the relevant data. - */ - std::vector fnArrIndexes; - - /// tunnel_route destination callback array. - static std::array, - OpId::MAX> - fix_destination; -}; - -class VerifyClient : public ActionItem -{ - uint8_t mode; - std::string ca_file; - std::string ca_dir; - -public: - VerifyClient(uint8_t param, std::string_view file, std::string_view dir) : mode(param), ca_file(file), ca_dir(dir) {} - VerifyClient(const char *param, std::string_view file, std::string_view dir) : VerifyClient(atoi(param), file, dir) {} - ~VerifyClient() override; - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - auto snis = TLSSNISupport::getInstance(&ssl); - auto ssl_vc = SSLNetVCAccess(&ssl); - - if (snis == nullptr || ssl_vc == nullptr) { - return SSL_TLSEXT_ERR_OK; - } - - const char *servername = snis->get_sni_server_name(); - Dbg(dbg_ctl_ssl_sni, "action verify param %d, fqdn [%s]", this->mode, servername); - setClientCertLevel(ssl_vc->ssl, this->mode); - ssl_vc->set_ca_cert_file(ca_file, ca_dir); - setClientCertCACerts(ssl_vc->ssl, ssl_vc->get_ca_cert_file(), ssl_vc->get_ca_cert_dir()); - - return SSL_TLSEXT_ERR_OK; - } - - bool - TestClientSNIAction(const char *servername, const IpEndpoint &ep, int &policy) const override - { - // This action is triggered by a SNI if it was set - return true; - } - - // No copying or moving. - VerifyClient(VerifyClient const &) = delete; - VerifyClient &operator=(VerifyClient const &) = delete; -}; - -class HostSniPolicy : public ActionItem -{ - uint8_t policy; - -public: - HostSniPolicy(const char *param) : policy(atoi(param)) {} - HostSniPolicy(uint8_t param) : policy(param) {} - ~HostSniPolicy() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - // On action this doesn't do anything - return SSL_TLSEXT_ERR_OK; - } - - bool - TestClientSNIAction(const char *servername, const IpEndpoint &ep, int &in_policy) const override - { - // Update the policy when testing - in_policy = this->policy; - // But this action didn't really trigger during the action phase - return false; - } -}; - -class TLSValidProtocols : public ActionItem -{ - bool unset = true; - unsigned long protocol_mask; - int min_ver = -1; - int max_ver = -1; - -public: -#ifdef SSL_OP_NO_TLSv1_3 - static const unsigned long max_mask = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2 | SSL_OP_NO_TLSv1_3; -#else - static const unsigned long max_mask = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2; -#endif - TLSValidProtocols() : protocol_mask(max_mask) {} - TLSValidProtocols(unsigned long protocols) : unset(false), protocol_mask(protocols) {} - TLSValidProtocols(int min_ver, int max_ver) : unset(false), protocol_mask(0), min_ver(min_ver), max_ver(max_ver) {} - - int - SNIAction(SSL &ssl, const Context & /* ctx */) const override - { - auto snis = TLSSNISupport::getInstance(&ssl); - auto tbs = TLSBasicSupport::getInstance(&ssl); - - if (snis == nullptr || tbs == nullptr) { - return SSL_TLSEXT_ERR_OK; - } - - if (this->min_ver >= 0 || this->max_ver >= 0) { - const char *servername = snis->get_sni_server_name(); - Dbg(dbg_ctl_ssl_sni, "TLSValidProtocol min=%d, max=%d, fqdn [%s]", this->min_ver, this->max_ver, servername); - tbs->set_valid_tls_version_min(this->min_ver); - tbs->set_valid_tls_version_max(this->max_ver); - } else { - if (!unset) { - const char *servername = snis->get_sni_server_name(); - Dbg(dbg_ctl_ssl_sni, "TLSValidProtocol param 0%x, fqdn [%s]", static_cast(this->protocol_mask), servername); - tbs->set_valid_tls_protocols(protocol_mask, TLSValidProtocols::max_mask); - Warning("valid_tls_versions_in is deprecated. Use valid_tls_version_min_in and ivalid_tls_version_max_in instead."); - } - } - - return SSL_TLSEXT_ERR_OK; - } -}; - -class SNI_IpAllow : public ActionItem -{ - swoc::IPRangeSet ip_addrs; - -public: - SNI_IpAllow(std::string &ip_allow_list, const std::string &servername); - - int SNIAction(SSL &ssl, const Context &ctx) const override; - - bool TestClientSNIAction(const char *servrername, const IpEndpoint &ep, int &policy) const override; - -protected: - /** Load the map from @a text. - * - * @param content A list of IP addresses in text form, separated by commas or newlines. - * @param server_name Server named, used only for debugging messages. - */ - void load(swoc::TextView content, swoc::TextView server_name); -}; - -/** - Override proxy.config.ssl.client.sni_policy by client_sni_policy in sni.yaml - */ -class OutboundSNIPolicy : public ActionItem -{ -public: - OutboundSNIPolicy(const std::string_view &p) : policy(p) {} - ~OutboundSNIPolicy() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { - if (!policy.empty()) { - if (auto snis = TLSSNISupport::getInstance(&ssl)) { - snis->hints_from_sni.outbound_sni_policy = policy; - } - } - return SSL_TLSEXT_ERR_OK; - } - -private: - std::string_view policy{}; -}; - -class ServerMaxEarlyData : public ActionItem -{ -public: - ServerMaxEarlyData(uint32_t value) -#if TS_HAS_TLS_EARLY_DATA - : server_max_early_data(value) -#endif - { - } - ~ServerMaxEarlyData() override {} - - int - SNIAction(SSL &ssl, const Context &ctx) const override - { -#if TS_HAS_TLS_EARLY_DATA - auto snis = TLSSNISupport::getInstance(&ssl); - auto eds = TLSEarlyDataSupport::getInstance(&ssl); - - if (snis == nullptr || eds == nullptr) { - return SSL_TLSEXT_ERR_OK; - } - - snis->hints_from_sni.server_max_early_data = server_max_early_data; - const uint32_t EARLY_DATA_DEFAULT_SIZE = 16384; - const uint32_t server_recv_max_early_data = - server_max_early_data > 0 ? std::max(server_max_early_data, EARLY_DATA_DEFAULT_SIZE) : 0; - eds->update_early_data_config(&ssl, server_max_early_data, server_recv_max_early_data); -#endif - return SSL_TLSEXT_ERR_OK; - } - -#if TS_HAS_TLS_EARLY_DATA -private: - uint32_t server_max_early_data = 0; -#endif -}; diff --git a/src/iocore/net/SNIActionPerformer.cc b/src/iocore/net/SNIActionPerformer.cc index c1f8169fcfc..e4caa2ae91f 100644 --- a/src/iocore/net/SNIActionPerformer.cc +++ b/src/iocore/net/SNIActionPerformer.cc @@ -26,7 +26,10 @@ #include "swoc/bwf_std.h" #include "swoc/bwf_ip.h" -#include "P_SNIActionPerformer.h" +#include "SNIActionPerformer.h" + +#include "P_SSLNextProtocolAccept.h" +#include "P_SSLNetVConnection.h" #if TS_USE_QUIC == 1 #include "P_QUICNetVConnection.h" @@ -58,6 +61,291 @@ ControlQUIC::SNIAction(SSL &ssl, const Context &ctx) const #endif } +int +ControlH2::SNIAction(SSL &ssl, const Context &ctx) const +{ + auto snis = TLSSNISupport::getInstance(&ssl); + auto alpns = ALPNSupport::getInstance(&ssl); + + if (snis == nullptr || alpns == nullptr) { + return SSL_TLSEXT_ERR_OK; + } + + const char *servername = snis->get_sni_server_name(); + if (!enable_h2) { + alpns->disableProtocol(TS_ALPN_PROTOCOL_INDEX_HTTP_2_0); + Dbg(dbg_ctl_ssl_sni, "H2 disabled, fqdn [%s]", servername); + } else { + alpns->enableProtocol(TS_ALPN_PROTOCOL_INDEX_HTTP_2_0); + Dbg(dbg_ctl_ssl_sni, "H2 enabled, fqdn [%s]", servername); + } + return SSL_TLSEXT_ERR_OK; +} + +int +HTTP2BufferWaterMark::SNIAction(SSL &ssl, const Context &ctx) const +{ + if (auto snis = TLSSNISupport::getInstance(&ssl)) { + snis->hints_from_sni.http2_buffer_water_mark = value; + } + return SSL_TLSEXT_ERR_OK; +} + +int +HTTP2InitialWindowSizeIn::SNIAction(SSL &ssl, const Context &ctx) const +{ + if (auto snis = TLSSNISupport::getInstance(&ssl)) { + snis->hints_from_sni.http2_initial_window_size_in = value; + } + return SSL_TLSEXT_ERR_OK; +} + +int +HTTP2MaxSettingsFramesPerMinute::SNIAction(SSL &ssl, const Context &ctx) const +{ + if (auto snis = TLSSNISupport::getInstance(&ssl)) { + snis->hints_from_sni.http2_max_settings_frames_per_minute = value; + } + return SSL_TLSEXT_ERR_OK; +} + +int +HTTP2MaxPingFramesPerMinute::SNIAction(SSL &ssl, const Context &ctx) const +{ + if (auto snis = TLSSNISupport::getInstance(&ssl)) { + snis->hints_from_sni.http2_max_ping_frames_per_minute = value; + } + return SSL_TLSEXT_ERR_OK; +} + +int +HTTP2MaxPriorityFramesPerMinute::SNIAction(SSL &ssl, const Context &ctx) const +{ + if (auto snis = TLSSNISupport::getInstance(&ssl)) { + snis->hints_from_sni.http2_max_priority_frames_per_minute = value; + } + return SSL_TLSEXT_ERR_OK; +} + +int +HTTP2MaxRstStreamFramesPerMinute::SNIAction(SSL &ssl, const Context &ctx) const +{ + if (auto snis = TLSSNISupport::getInstance(&ssl)) { + snis->hints_from_sni.http2_max_rst_stream_frames_per_minute = value; + } + return SSL_TLSEXT_ERR_OK; +} + +TunnelDestination::TunnelDestination(const std::string_view &dest, SNIRoutingType type, YamlSNIConfig::TunnelPreWarm prewarm, + const std::vector &alpn) + : destination(dest), type(type), tunnel_prewarm(prewarm), alpn_ids(alpn) +{ + // Check for port variable specification. Note that this is checked before + // the match group so that the corresponding function can be applied before + // the match group expansion(when the var_start_pos is still accurate). + auto recv_port_start_pos = destination.find(MAP_WITH_RECV_PORT_STR); + auto pp_port_start_pos = destination.find(MAP_WITH_PROXY_PROTOCOL_PORT_STR); + bool has_recv_port_var = recv_port_start_pos != std::string::npos; + bool has_pp_port_var = pp_port_start_pos != std::string::npos; + if (has_recv_port_var && has_pp_port_var) { + Error("Invalid destination \"%.*s\" in SNI configuration - Only one port variable can be specified.", + static_cast(destination.size()), destination.data()); + } else if (has_recv_port_var) { + fnArrIndexes.push_back(OpId::MAP_WITH_RECV_PORT); + var_start_pos = recv_port_start_pos; + } else if (has_pp_port_var) { + fnArrIndexes.push_back(OpId::MAP_WITH_PROXY_PROTOCOL_PORT); + var_start_pos = pp_port_start_pos; + } + // Check for match groups as well. + if (destination.find_first_of('$') != std::string::npos) { + fnArrIndexes.push_back(OpId::MATCH_GROUPS); + } +} + +int +TunnelDestination::SNIAction(SSL &ssl, const Context &ctx) const +{ + auto snis = TLSSNISupport::getInstance(&ssl); + auto tuns = TLSTunnelSupport::getInstance(&ssl); + auto alpns = ALPNSupport::getInstance(&ssl); + auto ssl_netvc = SSLNetVCAccess(&ssl); + + if (snis == nullptr || tuns == nullptr || alpns == nullptr || ssl_netvc == nullptr) { + return SSL_TLSEXT_ERR_OK; + } + + const char *servername = snis->get_sni_server_name(); + if (fnArrIndexes.empty()) { + tuns->set_tunnel_destination(destination, type, !TLSTunnelSupport::PORT_IS_DYNAMIC, tunnel_prewarm); + Debug("ssl_sni", "Destination now is [%s], fqdn [%s]", destination.c_str(), servername); + } else { + bool port_is_dynamic = false; + auto fixed_dst{destination}; + // Apply mapping functions to get the final destination. + for (auto fnArrIndex : fnArrIndexes) { + // Dispatch to the correct tunnel destination port function. + fixed_dst = fix_destination[fnArrIndex](fixed_dst, var_start_pos, ctx, ssl_netvc, port_is_dynamic); + } + tuns->set_tunnel_destination(fixed_dst, type, port_is_dynamic, tunnel_prewarm); + Debug("ssl_sni", "Destination now is [%s], configured [%s], fqdn [%s]", fixed_dst.c_str(), destination.c_str(), servername); + } + + if (type == SNIRoutingType::BLIND) { + ssl_netvc->attributes = HttpProxyPort::TRANSPORT_BLIND_TUNNEL; + } + + // ALPN + for (int id : alpn_ids) { + alpns->enableProtocol(id); + } + + return SSL_TLSEXT_ERR_OK; +} + +bool +TunnelDestination::is_number(std::string_view s) +{ + return !s.empty() && + std::find_if(std::begin(s), std::end(s), [](std::string_view::value_type c) { return !std::isdigit(c); }) == std::end(s); +} + +/** + * `tunnel_route` may contain matching groups ie: `$1` which needs to be replaced by the corresponding + * captured group from the `fqdn`, this function will replace them using proper group string. Matching + * groups could be at any order. + */ +std::string +TunnelDestination::replace_match_groups(std::string_view dst, const ActionItem::Context::CapturedGroupViewVec &groups, + bool &port_is_dynamic) +{ + port_is_dynamic = false; + if (dst.empty() || groups.empty()) { + return std::string{dst}; + } + std::string real_dst; + std::string::size_type pos{0}; + + const auto end = std::end(dst); + // We need to split the tunnel string and place each corresponding match on the + // configured one, so we need to first, get the match, then get the match number + // making sure that it does exist in the captured group. + bool is_writing_port = false; + for (auto c = std::begin(dst); c != end; c++, pos++) { + if (*c == ':') { + is_writing_port = true; + } + if (*c == '$') { + // find the next '.' so we can get the group number. + const auto dot = dst.find('.', pos); + std::string::size_type to = std::string::npos; + if (dot != std::string::npos) { + to = dot - (pos + 1); + } else { + // It may not have a dot, which could be because it's the last part. In that case + // we should check for the port separator. + if (const auto port = dst.find(':', pos); port != std::string::npos) { + to = (port - pos) - 1; + } + } + std::string_view number_str{dst.substr(pos + 1, to)}; + if (!is_number(number_str)) { + // it may be some issue on the configured string, place the char and keep going. + real_dst += *c; + continue; + } + const std::size_t group_index = swoc::svtoi(number_str); + if ((group_index - 1) < groups.size()) { + // place the captured group. + real_dst += groups[group_index - 1]; + if (is_writing_port) { + port_is_dynamic = true; + } + // if it was the last match, then ... + if (dot == std::string::npos && to == std::string::npos) { + // that's it. + break; + } + pos += number_str.size() + 1; + std::advance(c, number_str.size() + 1); + } + // If there is no match for a specific group, then we keep the `$#` as defined in the string. + } + real_dst += *c; + } + + return real_dst; +} + +int +VerifyClient::SNIAction(SSL &ssl, const Context &ctx) const +{ + auto snis = TLSSNISupport::getInstance(&ssl); + auto ssl_vc = SSLNetVCAccess(&ssl); + + if (snis == nullptr || ssl_vc == nullptr) { + return SSL_TLSEXT_ERR_OK; + } + + const char *servername = snis->get_sni_server_name(); + Dbg(dbg_ctl_ssl_sni, "action verify param %d, fqdn [%s]", this->mode, servername); + setClientCertLevel(ssl_vc->ssl, this->mode); + ssl_vc->set_ca_cert_file(ca_file, ca_dir); + setClientCertCACerts(ssl_vc->ssl, ssl_vc->get_ca_cert_file(), ssl_vc->get_ca_cert_dir()); + + return SSL_TLSEXT_ERR_OK; +} + +bool +VerifyClient::TestClientSNIAction(const char *servername, const IpEndpoint &ep, int &policy) const +{ + // This action is triggered by a SNI if it was set + return true; +} + +int +HostSniPolicy::SNIAction(SSL &ssl, const Context &ctx) const +{ + // On action this doesn't do anything + return SSL_TLSEXT_ERR_OK; +} + +bool +HostSniPolicy::TestClientSNIAction(const char *servername, const IpEndpoint &ep, int &in_policy) const +{ + // Update the policy when testing + in_policy = this->policy; + // But this action didn't really trigger during the action phase + return false; +} + +int +TLSValidProtocols::SNIAction(SSL &ssl, const Context & /* ctx */) const +{ + auto snis = TLSSNISupport::getInstance(&ssl); + auto tbs = TLSBasicSupport::getInstance(&ssl); + + if (snis == nullptr || tbs == nullptr) { + return SSL_TLSEXT_ERR_OK; + } + + if (this->min_ver >= 0 || this->max_ver >= 0) { + const char *servername = snis->get_sni_server_name(); + Dbg(dbg_ctl_ssl_sni, "TLSValidProtocol min=%d, max=%d, fqdn [%s]", this->min_ver, this->max_ver, servername); + tbs->set_valid_tls_version_min(this->min_ver); + tbs->set_valid_tls_version_max(this->max_ver); + } else { + if (!unset) { + const char *servername = snis->get_sni_server_name(); + Dbg(dbg_ctl_ssl_sni, "TLSValidProtocol param 0%x, fqdn [%s]", static_cast(this->protocol_mask), servername); + tbs->set_valid_tls_protocols(protocol_mask, TLSValidProtocols::max_mask); + Warning("valid_tls_versions_in is deprecated. Use valid_tls_version_min_in and ivalid_tls_version_max_in instead."); + } + } + + return SSL_TLSEXT_ERR_OK; +} + SNI_IpAllow::SNI_IpAllow(std::string &ip_allow_list, std::string const &servername) { swoc::TextView content{ip_allow_list}; @@ -122,3 +410,34 @@ SNI_IpAllow::TestClientSNIAction(char const *servrername, IpEndpoint const &ep, { return ip_addrs.contains(swoc::IPAddr(ep)); } + +int +OutboundSNIPolicy::SNIAction(SSL &ssl, const Context &ctx) const +{ + if (!policy.empty()) { + if (auto snis = TLSSNISupport::getInstance(&ssl)) { + snis->hints_from_sni.outbound_sni_policy = policy; + } + } + return SSL_TLSEXT_ERR_OK; +} + +int +ServerMaxEarlyData::SNIAction(SSL &ssl, const Context &ctx) const +{ +#if TS_HAS_TLS_EARLY_DATA + auto snis = TLSSNISupport::getInstance(&ssl); + auto eds = TLSEarlyDataSupport::getInstance(&ssl); + + if (snis == nullptr || eds == nullptr) { + return SSL_TLSEXT_ERR_OK; + } + + snis->hints_from_sni.server_max_early_data = server_max_early_data; + const uint32_t EARLY_DATA_DEFAULT_SIZE = 16384; + const uint32_t server_recv_max_early_data = + server_max_early_data > 0 ? std::max(server_max_early_data, EARLY_DATA_DEFAULT_SIZE) : 0; + eds->update_early_data_config(&ssl, server_max_early_data, server_recv_max_early_data); +#endif + return SSL_TLSEXT_ERR_OK; +} diff --git a/src/iocore/net/SNIActionPerformer.h b/src/iocore/net/SNIActionPerformer.h new file mode 100644 index 00000000000..46fa1e8737d --- /dev/null +++ b/src/iocore/net/SNIActionPerformer.h @@ -0,0 +1,304 @@ +/** @file + + SNI based Configuration in ATS + + @section license License + + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +#pragma once + +#include "swoc/TextView.h" +#include "swoc/swoc_ip.h" + +#include "iocore/eventsystem/EventSystem.h" +#include "iocore/net/SNIActionItem.h" +#include "iocore/net/SSLTypes.h" +#include "iocore/net/YamlSNIConfig.h" + +#include "tscore/ink_inet.h" + +#include + +class SSLNetVConnection; + +class ControlQUIC : public ActionItem +{ +public: +#if TS_USE_QUIC == 1 + ControlQUIC(bool turn_on) : enable_quic(turn_on) {} +#else + ControlQUIC(bool turn_on) {} +#endif + ~ControlQUIC() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: +#if TS_USE_QUIC == 1 + bool enable_quic = false; +#endif +}; + +class ControlH2 : public ActionItem +{ +public: + ControlH2(bool turn_on) : enable_h2(turn_on) {} + ~ControlH2() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + bool enable_h2 = false; +}; + +class HTTP2BufferWaterMark : public ActionItem +{ +public: + HTTP2BufferWaterMark(int value) : value(value) {} + ~HTTP2BufferWaterMark() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + int value = -1; +}; + +class HTTP2InitialWindowSizeIn : public ActionItem +{ +public: + HTTP2InitialWindowSizeIn(int value) : value(value) {} + ~HTTP2InitialWindowSizeIn() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + int value = -1; +}; + +class HTTP2MaxSettingsFramesPerMinute : public ActionItem +{ +public: + HTTP2MaxSettingsFramesPerMinute(int value) : value(value) {} + ~HTTP2MaxSettingsFramesPerMinute() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + int value = -1; +}; + +class HTTP2MaxPingFramesPerMinute : public ActionItem +{ +public: + HTTP2MaxPingFramesPerMinute(int value) : value(value) {} + ~HTTP2MaxPingFramesPerMinute() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + int value = -1; +}; + +class HTTP2MaxPriorityFramesPerMinute : public ActionItem +{ +public: + HTTP2MaxPriorityFramesPerMinute(int value) : value(value) {} + ~HTTP2MaxPriorityFramesPerMinute() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + int value = -1; +}; + +class HTTP2MaxRstStreamFramesPerMinute : public ActionItem +{ +public: + HTTP2MaxRstStreamFramesPerMinute(int value) : value(value) {} + ~HTTP2MaxRstStreamFramesPerMinute() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + int value = -1; +}; + +class TunnelDestination : public ActionItem +{ + // ID of the configured variable. This will be used to know which function + // should be called when processing the tunnel destination. + enum OpId : int32_t { + MATCH_GROUPS, // Deal with configured groups. + MAP_WITH_RECV_PORT, // Use port from inbound local + MAP_WITH_PROXY_PROTOCOL_PORT, // Use port from the proxy protocol + MAX // Always at the end and do not change the value of the above items. + }; + static constexpr std::string_view MAP_WITH_RECV_PORT_STR = "{inbound_local_port}"; + static constexpr std::string_view MAP_WITH_PROXY_PROTOCOL_PORT_STR = "{proxy_protocol_port}"; + +public: + TunnelDestination(const std::string_view &dest, SNIRoutingType type, YamlSNIConfig::TunnelPreWarm prewarm, + const std::vector &alpn); + ~TunnelDestination() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + static bool is_number(std::string_view s); + + /** + * `tunnel_route` may contain matching groups ie: `$1` which needs to be replaced by the corresponding + * captured group from the `fqdn`, this function will replace them using proper group string. Matching + * groups could be at any order. + */ + static std::string replace_match_groups(std::string_view dst, const ActionItem::Context::CapturedGroupViewVec &groups, + bool &port_is_dynamic); + + std::string destination; + + /// The start position of a tunnel destination variable, such as '{proxy_protocol_port}'. + size_t var_start_pos{0}; + SNIRoutingType type = SNIRoutingType::NONE; + YamlSNIConfig::TunnelPreWarm tunnel_prewarm = YamlSNIConfig::TunnelPreWarm::UNSET; + const std::vector &alpn_ids; + + /** The indexes of the mapping functions that need to be called. On + creation, we decide which functions need to be called, add the coressponding + indexes and then we call those functions with the relevant data. + */ + std::vector fnArrIndexes; + + /// tunnel_route destination callback array. + static std::array, + OpId::MAX> + fix_destination; +}; + +class VerifyClient : public ActionItem +{ + uint8_t mode; + std::string ca_file; + std::string ca_dir; + +public: + VerifyClient(uint8_t param, std::string_view file, std::string_view dir) : mode(param), ca_file(file), ca_dir(dir) {} + VerifyClient(const char *param, std::string_view file, std::string_view dir) : VerifyClient(atoi(param), file, dir) {} + ~VerifyClient() override; + + int SNIAction(SSL &ssl, const Context &ctx) const override; + + bool TestClientSNIAction(const char *servername, const IpEndpoint &ep, int &policy) const override; + + // No copying or moving. + VerifyClient(VerifyClient const &) = delete; + VerifyClient &operator=(VerifyClient const &) = delete; +}; + +class HostSniPolicy : public ActionItem +{ + uint8_t policy; + +public: + HostSniPolicy(const char *param) : policy(atoi(param)) {} + HostSniPolicy(uint8_t param) : policy(param) {} + ~HostSniPolicy() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + + bool TestClientSNIAction(const char *servername, const IpEndpoint &ep, int &in_policy) const override; +}; + +class TLSValidProtocols : public ActionItem +{ + bool unset = true; + unsigned long protocol_mask; + int min_ver = -1; + int max_ver = -1; + +public: +#ifdef SSL_OP_NO_TLSv1_3 + static const unsigned long max_mask = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2 | SSL_OP_NO_TLSv1_3; +#else + static const unsigned long max_mask = SSL_OP_NO_TLSv1 | SSL_OP_NO_TLSv1_1 | SSL_OP_NO_TLSv1_2; +#endif + TLSValidProtocols() : protocol_mask(max_mask) {} + TLSValidProtocols(unsigned long protocols) : unset(false), protocol_mask(protocols) {} + TLSValidProtocols(int min_ver, int max_ver) : unset(false), protocol_mask(0), min_ver(min_ver), max_ver(max_ver) {} + + int SNIAction(SSL &ssl, const Context & /* ctx */) const override; +}; + +class SNI_IpAllow : public ActionItem +{ + swoc::IPRangeSet ip_addrs; + +public: + SNI_IpAllow(std::string &ip_allow_list, const std::string &servername); + + int SNIAction(SSL &ssl, const Context &ctx) const override; + + bool TestClientSNIAction(const char *servrername, const IpEndpoint &ep, int &policy) const override; + +protected: + /** Load the map from @a text. + * + * @param content A list of IP addresses in text form, separated by commas or newlines. + * @param server_name Server named, used only for debugging messages. + */ + void load(swoc::TextView content, swoc::TextView server_name); +}; + +/** + Override proxy.config.ssl.client.sni_policy by client_sni_policy in sni.yaml + */ +class OutboundSNIPolicy : public ActionItem +{ +public: + OutboundSNIPolicy(const std::string_view &p) : policy(p) {} + ~OutboundSNIPolicy() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + std::string_view policy{}; +}; + +class ServerMaxEarlyData : public ActionItem +{ +public: + ServerMaxEarlyData(uint32_t value) +#if TS_HAS_TLS_EARLY_DATA + : server_max_early_data(value) +#endif + { + } + ~ServerMaxEarlyData() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +#if TS_HAS_TLS_EARLY_DATA +private: + uint32_t server_max_early_data = 0; +#endif +}; diff --git a/src/iocore/net/SSLSNIConfig.cc b/src/iocore/net/SSLSNIConfig.cc index d17b2415279..961dc8487b2 100644 --- a/src/iocore/net/SSLSNIConfig.cc +++ b/src/iocore/net/SSLSNIConfig.cc @@ -30,7 +30,10 @@ ****************************************************************************/ #include "iocore/net/SSLSNIConfig.h" -#include "P_SNIActionPerformer.h" +#include "iocore/net/SNIActionItem.h" + +#include "P_SSLUtils.h" +#include "P_SSLConfig.h" #include "tscore/Diags.h" #include "tscore/SimpleTokenizer.h" diff --git a/src/iocore/net/YamlSNIConfig.cc b/src/iocore/net/YamlSNIConfig.cc index 844e6a05621..05dcd5ee570 100644 --- a/src/iocore/net/YamlSNIConfig.cc +++ b/src/iocore/net/YamlSNIConfig.cc @@ -38,7 +38,9 @@ #include "swoc/TextView.h" #include "swoc/bwf_base.h" -#include "P_SNIActionPerformer.h" +#include "SNIActionPerformer.h" +#include "P_SSLConfig.h" +#include "P_SSLNetVConnection.h" #include "tscpp/util/ts_ip.h" diff --git a/src/proxy/http/HttpSM.cc b/src/proxy/http/HttpSM.cc index 24325f600a6..e987911eafc 100644 --- a/src/proxy/http/HttpSM.cc +++ b/src/proxy/http/HttpSM.cc @@ -50,6 +50,7 @@ #include "iocore/net/SSLSNIConfig.h" #include "iocore/net/TLSALPNSupport.h" #include "iocore/net/TLSBasicSupport.h" +#include "iocore/net/TLSSNISupport.h" #include "iocore/net/TLSSessionResumptionSupport.h" #include "iocore/net/TLSTunnelSupport.h" #include "proxy/http/HttpPages.h" diff --git a/src/proxy/http3/Http3SessionAccept.cc b/src/proxy/http3/Http3SessionAccept.cc index 188959845f0..0d3f9aed73e 100644 --- a/src/proxy/http3/Http3SessionAccept.cc +++ b/src/proxy/http3/Http3SessionAccept.cc @@ -26,6 +26,7 @@ #include "../../iocore/net/P_Net.h" #include "iocore/utils/Machine.h" #include "proxy/IPAllow.h" +#include "iocore/net/TLSSNISupport.h" #include "iocore/net/QUICSupport.h" #include "proxy/http3/Http09App.h"