This is an automated email from the ASF dual-hosted git repository. cmcfarlen pushed a commit to branch 10.1.x in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 76d5071207e4525b113ddc584e001adb41a2f08e Author: Jasmine Emanouel <[email protected]> AuthorDate: Fri May 30 07:25:26 2025 +1000 Add server_group_list option to sni (#12223) (cherry picked from commit fd02ff97ecc06df9a48038169f7bc58f5381dfd1) --- doc/admin-guide/files/sni.yaml.en.rst | 5 ++ include/iocore/net/TLSBasicSupport.h | 1 + include/iocore/net/YamlSNIConfig.h | 2 + src/iocore/net/SNIActionPerformer.cc | 19 +++++ src/iocore/net/SNIActionPerformer.h | 15 ++++ src/iocore/net/TLSBasicSupport.cc | 7 ++ src/iocore/net/YamlSNIConfig.cc | 8 ++- tests/gold_tests/tls/tls_sni_groups.test.py | 104 ++++++++++++++++++++++++++++ 8 files changed, 160 insertions(+), 1 deletion(-) diff --git a/doc/admin-guide/files/sni.yaml.en.rst b/doc/admin-guide/files/sni.yaml.en.rst index 2640f2f3e8..c83bb20c7a 100644 --- a/doc/admin-guide/files/sni.yaml.en.rst +++ b/doc/admin-guide/files/sni.yaml.en.rst @@ -167,6 +167,11 @@ server_TLSv1_3_cipher_suites Inbound Specifies an override to the documentation. Note that this configures the cipher suite used for inbound TLSv1.3 and above connections. +server_group_list Inbound Specifies an override to the global :ts:cv:`proxy.config.ssl.server.groups_list` + :file:`records.yaml` configuration. See the + `OpenSSL SSL_CTX_set_groups_list <https://docs.openssl.org/3.5/man3/SSL_CTX_set1_curves/>`_ + documentation. + host_sni_policy Inbound One of the values :code:`DISABLED`, :code:`PERMISSIVE`, or :code:`ENFORCED`. If not specified, the value of :ts:cv:`proxy.config.http.host_sni_policy` is used. diff --git a/include/iocore/net/TLSBasicSupport.h b/include/iocore/net/TLSBasicSupport.h index 799cda55c0..64ff16ea1a 100644 --- a/include/iocore/net/TLSBasicSupport.h +++ b/include/iocore/net/TLSBasicSupport.h @@ -63,6 +63,7 @@ public: void set_valid_tls_protocols(unsigned long proto_mask, unsigned long max_mask); void set_legacy_cipher_suite(std::string const &cipher_suite); void set_cipher_suite(std::string const &cipher_suite); + bool set_groups_list(std::string const &groups_list); /** * Give the plugin access to the data structure passed in during the underlying diff --git a/include/iocore/net/YamlSNIConfig.h b/include/iocore/net/YamlSNIConfig.h index 3a11dc5a57..c8396a76e7 100644 --- a/include/iocore/net/YamlSNIConfig.h +++ b/include/iocore/net/YamlSNIConfig.h @@ -58,6 +58,7 @@ TSDECL(client_key); TSDECL(client_sni_policy); TSDECL(server_cipher_suite); TSDECL(server_TLSv1_3_cipher_suites); +TSDECL(server_groups_list); TSDECL(ip_allow); TSDECL(valid_tls_versions_in); TSDECL(valid_tls_version_min_in); @@ -105,6 +106,7 @@ struct YamlSNIConfig { std::string client_sni_policy; std::string server_cipher_suite; std::string server_TLSv1_3_cipher_suites; + std::string server_groups_list; std::string ip_allow; bool protocol_unset = true; unsigned long protocol_mask; diff --git a/src/iocore/net/SNIActionPerformer.cc b/src/iocore/net/SNIActionPerformer.cc index 3a83a5772a..107a1552b3 100644 --- a/src/iocore/net/SNIActionPerformer.cc +++ b/src/iocore/net/SNIActionPerformer.cc @@ -493,3 +493,22 @@ ServerTLSv1_3CipherSuites::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED tbs->set_cipher_suite(server_TLSV1_3_cipher_suites); return SSL_TLSEXT_ERR_OK; } + +int +ServerGroupsList::SNIAction(SSL &ssl, const Context & /* ctx ATS_UNUSED */) const +{ + if (server_groups_list.empty()) { + return SSL_TLSEXT_ERR_OK; + } + auto tbs = TLSBasicSupport::getInstance(&ssl); + if (tbs == nullptr) { + return SSL_TLSEXT_ERR_OK; + } + Dbg(dbg_ctl_ssl_sni, "Setting groups list from server_groups_list to %s", server_groups_list.c_str()); + + if (!tbs->set_groups_list(server_groups_list)) { + Error("Invalid server_groups_list: %s", server_groups_list.c_str()); + return SSL_TLSEXT_ERR_ALERT_WARNING; + } + return SSL_TLSEXT_ERR_OK; +} diff --git a/src/iocore/net/SNIActionPerformer.h b/src/iocore/net/SNIActionPerformer.h index f2e8b60e26..c173caacaa 100644 --- a/src/iocore/net/SNIActionPerformer.h +++ b/src/iocore/net/SNIActionPerformer.h @@ -342,3 +342,18 @@ public: private: std::string const server_TLSV1_3_cipher_suites{}; }; + +/** + Override proxy.config.ssl.server.groups_list by server_groups_list in sni.yaml + */ +class ServerGroupsList : public ActionItem +{ +public: + ServerGroupsList(std::string const &p) : server_groups_list(p) {} + ~ServerGroupsList() override {} + + int SNIAction(SSL &ssl, const Context &ctx) const override; + +private: + std::string const server_groups_list{}; +}; diff --git a/src/iocore/net/TLSBasicSupport.cc b/src/iocore/net/TLSBasicSupport.cc index 0c3a5de50d..dc3a053657 100644 --- a/src/iocore/net/TLSBasicSupport.cc +++ b/src/iocore/net/TLSBasicSupport.cc @@ -188,6 +188,13 @@ TLSBasicSupport::set_cipher_suite([[maybe_unused]] std::string const &cipher_sui #endif } +bool +TLSBasicSupport::set_groups_list(std::string const &groups_list) +{ + auto ssl = this->_get_ssl_object(); + return SSL_set1_groups_list(ssl, groups_list.c_str()); +} + int TLSBasicSupport::verify_certificate(X509_STORE_CTX *ctx) { diff --git a/src/iocore/net/YamlSNIConfig.cc b/src/iocore/net/YamlSNIConfig.cc index c5dfd70c7d..bbc0eb4ace 100644 --- a/src/iocore/net/YamlSNIConfig.cc +++ b/src/iocore/net/YamlSNIConfig.cc @@ -161,6 +161,9 @@ YamlSNIConfig::Item::populate_sni_actions(action_vector_t &actions) if (!server_TLSv1_3_cipher_suites.empty()) { actions.push_back(std::make_unique<ServerTLSv1_3CipherSuites>(server_TLSv1_3_cipher_suites)); } + if (!server_groups_list.empty()) { + actions.push_back(std::make_unique<ServerGroupsList>(server_groups_list)); + } if (http2_buffer_water_mark.has_value()) { actions.push_back(std::make_unique<HTTP2BufferWaterMark>(http2_buffer_water_mark.value())); } @@ -226,6 +229,7 @@ std::set<std::string> valid_sni_config_keys = {TS_fqdn, #if TS_USE_TLS_SET_CIPHERSUITES TS_server_TLSv1_3_cipher_suites, #endif + TS_server_groups_list, TS_http2, TS_http2_buffer_water_mark, TS_http2_initial_window_size_in, @@ -458,7 +462,9 @@ template <> struct convert<YamlSNIConfig::Item> { if (node[TS_server_TLSv1_3_cipher_suites]) { item.server_TLSv1_3_cipher_suites = node[TS_server_TLSv1_3_cipher_suites].as<std::string>(); } - + if (node[TS_server_groups_list]) { + item.server_groups_list = node[TS_server_groups_list].as<std::string>(); + } if (node[TS_ip_allow]) { item.ip_allow = node[TS_ip_allow].as<std::string>(); } diff --git a/tests/gold_tests/tls/tls_sni_groups.test.py b/tests/gold_tests/tls/tls_sni_groups.test.py new file mode 100644 index 0000000000..870f07de0e --- /dev/null +++ b/tests/gold_tests/tls/tls_sni_groups.test.py @@ -0,0 +1,104 @@ +''' +''' +# 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. + +Test.Summary = ''' +Test SNI configuration server_groups_list +''' +# The groups function was added in OpenSSL 1.1.1 +Test.SkipUnless(Condition.HasOpenSSLVersion("1.1.1")) + +# Define default ATS +ts = Test.MakeATSProcess("ts", enable_tls=True) +server = Test.MakeOriginServer("server", ssl=True) + +request_header = {"headers": "GET / HTTP/1.1\r\n\r\n", "timestamp": "1469733493.993", "body": ""} +response_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n", "timestamp": "1469733493.993", "body": "foo ok"} +server.addResponse("sessionlog.json", request_header, response_header) + +# add ssl materials like key, certificates for the server +ts.addSSLfile("ssl/server.pem") +ts.addSSLfile("ssl/server.key") + +# Need no remap rules. Everything should be processed by sni + +# Make sure the TS server certs are different from the origin certs +ts.Disk.ssl_multicert_config.AddLine('dest_ip=* ssl_cert_name=server.pem ssl_key_name=server.key') + +ts.Disk.records_config.update( + { + 'proxy.config.ssl.server.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.server.private_key.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.ssl.client.CA.cert.path': '{0}'.format(ts.Variables.SSLDir), + 'proxy.config.diags.debug.enabled': 1, + 'proxy.config.diags.debug.tags': 'ssl_sni', + }) + +ts.Disk.sni_yaml.AddLines( + [ + 'sni:', + '- fqdn: aaa.com', + ' server_groups_list: X25519MLKEM768', + ' valid_tls_versions_in: [ TLSv1_3 ]', + ' server_TLSv1_3_cipher_suites: TLS_AES_256_GCM_SHA384', + '- fqdn: bbb.com', + ' server_groups_list: x25519', + ' valid_tls_versions_in: [ TLSv1_2 ]', + ' server_cipher_suite: ECDHE-RSA-AES256-GCM-SHA384', + '- fqdn: ccc.com', + ' server_groups_list: ABC123', + ' valid_tls_versions_in: [ TLSv1_2 ]', + ' server_cipher_suite: ECDHE-RSA-AES256-GCM-SHA384', + ]) + +tr = Test.AddTestRun("Test 0: x25519") +tr.Processes.Default.StartBefore(server) +tr.Processes.Default.StartBefore(Test.Processes.ts) +tr.MakeCurlCommand( + "-v --ciphers ECDHE-RSA-AES256-GCM-SHA384 --resolve 'bbb.com:{0}:127.0.0.1' -k https://bbb.com:{0}".format( + ts.Variables.ssl_port)) +tr.ReturnCode = 0 +tr.StillRunningAfter = ts +ts.Disk.traffic_out.Content += Testers.ContainsExpression( + "Setting groups list from server_groups_list to x25519", "Should log setting the server groups") +tr.Processes.Default.Streams.all = Testers.IncludesExpression( + f"SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384 / x25519", "Curl should log using x25519 in the SSL connection") + +tr = Test.AddTestRun("Test 1: fail") +tr.MakeCurlCommand( + "-v --ciphers ECDHE-RSA-AES256-GCM-SHA384 --resolve 'ccc.com:{0}:127.0.0.1' -k https://ccc.com:{0}".format( + ts.Variables.ssl_port)) +# The error code is 35, which indicates there was a ssl connection error +tr.ReturnCode = 35 +tr.StillRunningAfter = ts +tr.StillRunningAfter = server +ts.Disk.diags_log.Content = Testers.ContainsExpression( + "ERROR: Invalid server_groups_list: ABC123", "Curl attempt should have failed") + +# Hybrid ECDH PQ key exchange TLS groups were added in OpenSSL 3.5 +if Condition.HasOpenSSLVersion("3.5.0"): + tr = Test.AddTestRun("Test 2: X25519MLKEM768") + tr.MakeCurlCommand( + "-v --tls13-ciphers TLS_AES_256_GCM_SHA384 --resolve 'aaa.com:{0}:127.0.0.1' -k https://aaa.com:{0}".format( + ts.Variables.ssl_port)) + tr.ReturnCode = 0 + tr.StillRunningAfter = ts + ts.Disk.traffic_out.Content += Testers.ContainsExpression( + "Setting groups list from server_groups_list to X25519MLKEM768", "Should log setting the server groups") + tr.Processes.Default.Streams.all = Testers.IncludesExpression( + f"SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384 / X25519MLKEM768", + f"Curl should log using X25519MLKEM768 in the SSL connection")
