From 6ada817de90e90699a1ba26d4ae8da8961912ce3 Mon Sep 17 00:00:00 2001 From: Leif Hedstrom Date: Mon, 15 Jul 2019 13:35:04 -0600 Subject: [PATCH] Allows to override HTTP/2 on per SNI / context This means you can now enable H2 per domain, without enabling HTTP/2 globally in the ports specifications. --- configs/sni.yaml.default | 4 +- doc/admin-guide/files/sni.yaml.en.rst | 10 ++--- iocore/net/P_SNIActionPerformer.h | 32 ++++++++------- iocore/net/SNIActionPerformer.cc | 58 --------------------------- iocore/net/SSLSNIConfig.cc | 20 ++++++--- iocore/net/YamlSNIConfig.cc | 6 +-- iocore/net/YamlSNIConfig.h | 15 +++---- tests/gold_tests/h2/h2disable.test.py | 13 +++--- 8 files changed, 56 insertions(+), 102 deletions(-) delete mode 100644 iocore/net/SNIActionPerformer.cc diff --git a/configs/sni.yaml.default b/configs/sni.yaml.default index 6b9080c39e6..5bfe8a83a14 100644 --- a/configs/sni.yaml.default +++ b/configs/sni.yaml.default @@ -7,7 +7,7 @@ # YAML-based Configuration file # Format : # Actions available: -# disable_h2 - removes H2 from the protocol list advertised by ATS; parameter required = None, parameters = true or false +# http2 - enable or disable HTTP/2 from the protocol list advertised by ATS; parameters = 'on' or 'off' (boolean) # verify_client - sets the verification flag for verifying the client certificate; parameters = one of 'NONE', 'MODERATE' or 'STRICT' # verify_origin_server - sets the verification flag for verifying the server certificate; parameters = one of 'NONE', 'MODERATE' or 'STRICT' # client_cert - sets the client certificate to present to the server specified in dest_host; parameters = certificate file . @@ -19,7 +19,7 @@ # Example: # sni: # - fqdn: one.com -# disable_h2: true +# http2: off # verify_origin_server: STRICT # client_cert: somepem.pem # verify_client: MODERATE diff --git a/doc/admin-guide/files/sni.yaml.en.rst b/doc/admin-guide/files/sni.yaml.en.rst index 98f2dffebaa..37790a4c673 100644 --- a/doc/admin-guide/files/sni.yaml.en.rst +++ b/doc/admin-guide/files/sni.yaml.en.rst @@ -98,11 +98,11 @@ client_key The file containing the client private key that corres :ts:cv:`proxy.config.ssl.client.private_key.filename` is used. -disable_h2 :code:`true` or :code:`false`. +http2 :code:`on` or :code:`off`. - If :code:`false` then HTTP/2 is removed from - the valid next protocol list. It is not an error to set this to :code:`false` - for proxy ports on which HTTP/2 is not enabled. + If :code:`off` then HTTP/2 is removed from the valid next protocol list. If + :code:`on`, then HTTP/2 is added to the next protocol list, regardless if the + proxy port specification has not explicitly enabled it. tunnel_route Destination as an FQDN and port, separated by a colon ``:``. @@ -175,7 +175,7 @@ Disable HTTP/2 for ``no-http2.example.com``. sni: - fqdn: no-http2.example.com - disable_h2: true + http2: off Require client certificate verification for ``example.com`` and any server name ending with ``.yahoo.com``. Therefore, client request for a server name ending with yahoo.com (e.g., def.yahoo.com, abc.yahoo.com etc.) will cause |TS| require and verify the client certificate. By contrast, |TS| will allow a client certificate to be provided for ``example.com`` and if it is, |TS| will require the certificate to be valid. diff --git a/iocore/net/P_SNIActionPerformer.h b/iocore/net/P_SNIActionPerformer.h index 957ff3c055a..4df4fd8e895 100644 --- a/iocore/net/P_SNIActionPerformer.h +++ b/iocore/net/P_SNIActionPerformer.h @@ -36,15 +36,8 @@ #include "tscore/ink_inet.h" #include -extern std::unordered_map snpsMap; - -/*// enum of all the actions -enum AllActions { - TS_DISABLE_H2 = 0, - TS_VERIFY_CLIENT, // this applies to server side vc only - TS_TUNNEL_ROUTE, // blind tunnel action -}; -*/ +extern std::unordered_map snpDisableH2Map; +extern std::unordered_map snpEnableH2Map; /** action for setting next hop properties should be listed in the following enum*/ /* enum PropertyActions { TS_VERIFY_SERVER = 200, TS_CLIENT_CERT }; */ @@ -56,24 +49,33 @@ class ActionItem virtual ~ActionItem(){}; }; -class DisableH2 : public ActionItem +class ControlH2 : public ActionItem { public: - DisableH2() {} - ~DisableH2() override {} + ControlH2(bool flag) : enabled(flag) {} + ~ControlH2() override {} int SNIAction(Continuation *cont) const override { - auto ssl_vc = dynamic_cast(cont); - auto accept_obj = ssl_vc ? ssl_vc->accept_object : nullptr; + SSLNetVConnection *ssl_vc = dynamic_cast(cont); + NetAccept *accept_obj = ssl_vc ? ssl_vc->accept_object : nullptr; + if (accept_obj && accept_obj->snpa && ssl_vc) { - if (auto it = snpsMap.find(accept_obj->id); it != snpsMap.end()) { + // This is a bit of hack, because ALPN strings are managed via accecpt ports as well as via plugin APIs. + // Cloning the port defined NextProtocolSet on every request would be rather expensive, with a number + // of memory allocations (calling new()). See Issue # + auto snpMap = enabled ? &snpEnableH2Map : &snpDisableH2Map; + + if (auto it = snpMap->find(accept_obj->id); it != snpMap->end()) { ssl_vc->registerNextProtocolSet(it->second); } } return SSL_TLSEXT_ERR_OK; } + +private: + bool enabled = false; }; class TunnelDestination : public ActionItem diff --git a/iocore/net/SNIActionPerformer.cc b/iocore/net/SNIActionPerformer.cc deleted file mode 100644 index 842345e34b9..00000000000 --- a/iocore/net/SNIActionPerformer.cc +++ /dev/null @@ -1,58 +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++ -*- ****************************** - SNIActionPerformer.cc - Created On : 05/02/2017 - - Description: - SNI based Configuration in ATS - ****************************************************************************/ -#include "P_SNIActionPerformer.h" -#include "tscore/ink_memory.h" -#include "P_SSLSNI.h" -#include "P_Net.h" -#include "P_SSLNextProtocolAccept.h" -#include "P_SSLUtils.h" - -extern std::unordered_map snpsMap; - -int -SNIActionPerformer::PerformAction(Continuation *cont, const char *servername) -{ - SNIConfig::scoped_config params; - auto actionvec = params->get(servername); - if (!actionvec) { - Debug("ssl_sni", "%s not available in the map", servername); - } else { - for (auto it : *actionvec) { - if (it) { - auto ret = it->SNIAction(cont); - if (ret != SSL_TLSEXT_ERR_OK) { - return ret; - } - } - } - } - return SSL_TLSEXT_ERR_OK; -} diff --git a/iocore/net/SSLSNIConfig.cc b/iocore/net/SSLSNIConfig.cc index 895f6988850..ebdba5fa848 100644 --- a/iocore/net/SSLSNIConfig.cc +++ b/iocore/net/SSLSNIConfig.cc @@ -41,7 +41,10 @@ static ConfigUpdateHandler *sniConfigUpdate; struct NetAccept; -std::unordered_map snpsMap; + +// This is a hack ... See Issue #5718. +std::unordered_map snpDisableH2Map; +std::unordered_map snpEnableH2Map; const NextHopProperty * SNIConfigParams::getPropertyConfig(const std::string &servername) const @@ -66,8 +69,8 @@ SNIConfigParams::loadSNIConfig() Debug("ssl", "name: %s", item.fqdn.data()); // set SNI based actions to be called in the ssl_servername_only callback - if (item.disable_h2) { - ai->actions.push_back(std::make_unique()); + if (item.control_http2.has_value()) { + ai->actions.push_back(std::make_unique(item.control_http2.value())); } if (item.verify_client_level != 255) { ai->actions.push_back(std::make_unique(item.verify_client_level)); @@ -168,9 +171,14 @@ SNIConfig::cloneProtoSet() SCOPED_MUTEX_LOCK(lock, naVecMutex, this_ethread()); for (auto na : naVec) { if (na->snpa) { - auto snps = na->snpa->cloneProtoSet(); - snps->unregisterEndpoint(TS_ALPN_PROTOCOL_HTTP_2_0, nullptr); - snpsMap.emplace(na->id, snps); + auto enable_h2 = na->snpa->cloneProtoSet(); + auto disable_h2 = na->snpa->cloneProtoSet(); + + disable_h2->unregisterEndpoint(TS_ALPN_PROTOCOL_HTTP_2_0, nullptr); + snpDisableH2Map.emplace(na->id, disable_h2); + + enable_h2->registerEndpoint(TS_ALPN_PROTOCOL_HTTP_2_0, nullptr); + snpEnableH2Map.emplace(na->id, enable_h2); } } } diff --git a/iocore/net/YamlSNIConfig.cc b/iocore/net/YamlSNIConfig.cc index d2f6f9d8c7d..4291057e2ab 100644 --- a/iocore/net/YamlSNIConfig.cc +++ b/iocore/net/YamlSNIConfig.cc @@ -94,7 +94,7 @@ TsEnumDescriptor PROPERTIES_DESCRIPTOR = {{{"NONE", 0}, {"SIGNATURE", 0x1}, { TsEnumDescriptor TLS_PROTOCOLS_DESCRIPTOR = {{{"TLSv1", 0}, {"TLSv1_1", 1}, {"TLSv1_2", 2}, {"TLSv1_3", 3}}}; std::set valid_sni_config_keys = {TS_fqdn, - TS_disable_h2, + TS_http2, TS_verify_client, TS_tunnel_route, TS_forward_route, @@ -128,8 +128,8 @@ template <> struct convert { } else { return false; // servername must be present } - if (node[TS_disable_h2]) { - item.disable_h2 = node[TS_disable_h2].as(); + if (node[TS_http2]) { + item.control_http2 = node[TS_http2].as(); } // enum diff --git a/iocore/net/YamlSNIConfig.h b/iocore/net/YamlSNIConfig.h index 0e6b8fc549f..c8ae5856299 100644 --- a/iocore/net/YamlSNIConfig.h +++ b/iocore/net/YamlSNIConfig.h @@ -23,12 +23,13 @@ #include #include +#include #include "tscore/Errata.h" #define TSDECL(id) constexpr char TS_##id[] = #id TSDECL(fqdn); -TSDECL(disable_h2); +TSDECL(http2); TSDECL(verify_client); TSDECL(tunnel_route); TSDECL(forward_route); @@ -44,7 +45,7 @@ TSDECL(valid_tls_versions_in); const int start = 0; struct YamlSNIConfig { enum class Action { - disable_h2 = start, + control_http2 = start, verify_client, tunnel_route, // blind tunnel action forward_route, // decrypt data and then blind tunnel action @@ -61,17 +62,17 @@ struct YamlSNIConfig { struct Item { std::string fqdn; - bool disable_h2 = false; - uint8_t verify_client_level = 255; - std::string tunnel_destination; + uint8_t verify_client_level = 255; bool tunnel_decrypt = false; Policy verify_server_policy = Policy::UNSET; Property verify_server_properties = Property::UNSET; + bool protocol_unset = true; + unsigned long protocol_mask = 0; + std::optional control_http2; // Has no value by default, so do not initialize! + std::string tunnel_destination; std::string client_cert; std::string client_key; std::string ip_allow; - bool protocol_unset = true; - unsigned long protocol_mask; void EnableProtocol(YamlSNIConfig::TLSProtocol proto); }; diff --git a/tests/gold_tests/h2/h2disable.test.py b/tests/gold_tests/h2/h2disable.test.py index b784665e7d4..b2ca3737267 100644 --- a/tests/gold_tests/h2/h2disable.test.py +++ b/tests/gold_tests/h2/h2disable.test.py @@ -57,11 +57,11 @@ }) ts.Disk.sni_yaml.AddLines([ - 'sni:', - '- fqdn: bar.com', - ' disable_h2: true', - '- fqdn: bob.*.com', - ' disable_h2: true', + 'sni:', + '- fqdn: bar.com', + ' http2: off', + '- fqdn: bob.*.com', + ' http2: off', ]) tr = Test.AddTestRun("Negotiate-h2") @@ -87,7 +87,8 @@ tr2.TimeOut = 5 tr2 = Test.AddTestRun("Do not negotiate h2") -tr2.Processes.Default.Command = "curl -v -k --resolve 'bob.foo.com:{0}:127.0.0.1' https://bob.foo.com:{0}".format(ts.Variables.ssl_port) +tr2.Processes.Default.Command = "curl -v -k --resolve 'bob.foo.com:{0}:127.0.0.1' https://bob.foo.com:{0}".format( + ts.Variables.ssl_port) tr2.ReturnCode = 0 tr2.StillRunningAfter = server tr2.Processes.Default.TimeOut = 5