This is an automated email from the ASF dual-hosted git repository.

cmcfarlen pushed a commit to branch 10.2.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git

commit 2552e83d2dfd5eb3322031e05d7d167114b10688
Author: Brian Olsen <[email protected]>
AuthorDate: Tue Mar 17 12:43:15 2026 -0600

    add host_override to parent.config and other sni name fixes (#12868)
    
    * add host_override to parent.config and other sni name fixes
    
    * add documentation for host_override in parent.config, update 
strategies.yaml docs
    
    * add test using pristine_host_hdr setting
    
    ---------
    
    Co-authored-by: Brian Olsen <[email protected]>
    (cherry picked from commit 801e5bebad78b6546ebd1b6658bdafc33cd8feba)
---
 doc/admin-guide/files/parent.config.en.rst         |   8 +
 doc/admin-guide/files/strategies.yaml.en.rst       |   2 +-
 include/proxy/ParentSelection.h                    |  15 ++
 .../proxy/http/remap/NextHopSelectionStrategy.h    |   3 -
 src/proxy/ParentSelection.cc                       |   7 +
 src/proxy/http/HttpSM.cc                           |   6 +-
 src/proxy/http/HttpTransact.cc                     |  73 ++++++++-
 src/proxy/http/remap/NextHopConsistentHash.cc      |   4 -
 src/proxy/http/remap/NextHopRoundRobin.cc          |   1 -
 src/proxy/http/remap/NextHopSelectionStrategy.cc   |  10 --
 tests/gold_tests/tls/ssl/gen_foobar_certs.sh       |  29 ++++
 tests/gold_tests/tls/ssl/server-bar.key            |  28 ++++
 tests/gold_tests/tls/ssl/server-bar.pem            |  22 +++
 tests/gold_tests/tls/ssl/server-foo.key            |  28 ++++
 tests/gold_tests/tls/ssl/server-foo.pem            |  22 +++
 .../gold_tests/tls/tls_sni_parent_failover.test.py | 174 +++++++++++++++++++++
 16 files changed, 402 insertions(+), 30 deletions(-)

diff --git a/doc/admin-guide/files/parent.config.en.rst 
b/doc/admin-guide/files/parent.config.en.rst
index 7b8fa4a56d..64fa4d8974 100644
--- a/doc/admin-guide/files/parent.config.en.rst
+++ b/doc/admin-guide/files/parent.config.en.rst
@@ -324,6 +324,14 @@ The following list shows the possible actions and their 
allowed values.
 
   -  ``false`` - The default.  Do not ignore the host status.
 
+.. _parent-config-format-host_override:
+
+``host_override``
+    One of the following values:
+
+    -  ``true`` - Sets the host header of the request using the selected 
parent, including the SNI name to be used for any upstream TLS connection.  
Useful when a parent is another CDN that requires a correct SNI name.
+    -  ``false`` - The default.  Does not change the host header.
+
 .. _parent-config-format-hash-algorithm:
 
 ``hash_algorithm``
diff --git a/doc/admin-guide/files/strategies.yaml.en.rst 
b/doc/admin-guide/files/strategies.yaml.en.rst
index ec7c223c8a..7e9852f830 100644
--- a/doc/admin-guide/files/strategies.yaml.en.rst
+++ b/doc/admin-guide/files/strategies.yaml.en.rst
@@ -286,7 +286,7 @@ Each **strategy** in the list may using the following 
parameters:
     (**self** should only be necessary when the local hostname can only be 
translated to an IP address
     with a DNS lookup.)
 
-- **host_override**: A boolean value that will set the host header to the 
selected parent rather than the original host. **true** sets host header to 
selected parent. **false** (default) leaves the host header untouched.
+- **host_override**: A boolean value that will set the host header to the 
selected parent rather than the original host, including the SNI name for any 
attempted upstream TLS connection. **true** sets host header to selected 
parent. **false** (default) leaves the host header untouched.
 
 Example:
 ::
diff --git a/include/proxy/ParentSelection.h b/include/proxy/ParentSelection.h
index 5960cfda18..8bfd556fcb 100644
--- a/include/proxy/ParentSelection.h
+++ b/include/proxy/ParentSelection.h
@@ -167,6 +167,7 @@ public:
   int                             max_unavailable_server_retries     = 1;
   int                             secondary_mode                     = 1;
   bool                            ignore_self_detect                 = false;
+  bool                            host_override                      = false;
   ParentHashAlgorithm             consistent_hash_algorithm          = 
ParentHashAlgorithm::SIPHASH24;
   uint64_t                        consistent_hash_seed0              = 0;
   uint64_t                        consistent_hash_seed1              = 0;
@@ -242,6 +243,20 @@ struct ParentResult {
     return is_api_result() ? ParentRetry_t::NONE : rec->parent_retry;
   }
 
+  bool
+  host_override() const
+  {
+    if (is_api_result()) {
+      return false;
+    }
+
+    if (!is_some()) {
+      return false;
+    }
+
+    return rec->host_override;
+  }
+
   unsigned
   max_retries(ParentRetry_t method) const
   {
diff --git a/include/proxy/http/remap/NextHopSelectionStrategy.h 
b/include/proxy/http/remap/NextHopSelectionStrategy.h
index 25e6267615..0623a9b440 100644
--- a/include/proxy/http/remap/NextHopSelectionStrategy.h
+++ b/include/proxy/http/remap/NextHopSelectionStrategy.h
@@ -27,7 +27,6 @@
 #include <utility>
 
 #include "ts/apidefs.h"
-#include "proxy/ParentSelection.h"
 #include "proxy/http/HttpTransact.h"
 
 #ifndef _NH_UNIT_TESTS_
@@ -199,8 +198,6 @@ public:
                            const time_t now = 0);
   bool         nextHopExists(TSHttpTxn txnp, void *ih = nullptr);
 
-  void setHostHeader(TSHttpTxn txnp, const char *hostname);
-
   virtual ParentRetry_t responseIsRetryable(int64_t sm_id, 
HttpTransact::CurrentInfo &current_info, HTTPStatus response_code);
 
   void retryComplete(TSHttpTxn txn, const char *hostname, const int port);
diff --git a/src/proxy/ParentSelection.cc b/src/proxy/ParentSelection.cc
index 0870bcb9bf..b282fba7b3 100644
--- a/src/proxy/ParentSelection.cc
+++ b/src/proxy/ParentSelection.cc
@@ -797,6 +797,13 @@ ParentRecord::Init(matcher_line *line_info)
         ignore_self_detect = false;
       }
       used = true;
+    } else if (strcasecmp(label, "host_override") == 0) {
+      if (strcasecmp(val, "true") == 0) {
+        host_override = true;
+      } else {
+        host_override = false;
+      }
+      used = true;
     } else if (strcasecmp(label, "hash_algorithm") == 0) {
       consistent_hash_algorithm = parseHashAlgorithm(val);
       used                      = true;
diff --git a/src/proxy/http/HttpSM.cc b/src/proxy/http/HttpSM.cc
index 82e39698e0..4b977c1bc6 100644
--- a/src/proxy/http/HttpSM.cc
+++ b/src/proxy/http/HttpSM.cc
@@ -5307,11 +5307,11 @@ HttpSM::get_outbound_sni() const
     // By default the host header field value is used for the SNI.
     zret = t_state.hdr_info.server_request.host_get();
   } else if (_ua.get_txn() && policy == "server_name"_tv) {
-    const char *server_name = snis->get_sni_server_name();
-    if (server_name[0] == '\0') {
+    const char *const server_name = snis->get_sni_server_name();
+    if (nullptr == server_name || server_name[0] == '\0') {
       zret.assign(nullptr, swoc::TextView::npos);
     } else {
-      zret.assign(snis->get_sni_server_name(), swoc::TextView::npos);
+      zret.assign(server_name, swoc::TextView::npos);
     }
   } else if (policy.front() == '@') { // guaranteed non-empty from previous 
clause
     zret = policy.remove_prefix(1);
diff --git a/src/proxy/http/HttpTransact.cc b/src/proxy/http/HttpTransact.cc
index 4996a07cce..46562e5ed7 100644
--- a/src/proxy/http/HttpTransact.cc
+++ b/src/proxy/http/HttpTransact.cc
@@ -139,19 +139,51 @@ bypass_ok(HttpTransact::State *s)
   return false;
 }
 
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static bool
+host_override(HttpTransact::State *s)
+{
+  if (s->response_action.handled) { // should be handled by the plugin
+    return false;
+  } else if (nullptr != s->next_hop_strategy) {
+    // remap strategies do not support the TSHttpTxnParentProxySet API.
+    return s->next_hop_strategy->host_override;
+  } else if (nullptr != s->parent_params) {
+    return s->parent_result.host_override();
+  }
+  return false;
+}
+
+// wrapper to choose between a remap next hop strategy or use parent.config
+// remap next hop strategy is preferred
+inline static bool
+is_some(HttpTransact::State *s)
+{
+  if (s->response_action.handled) {
+    return true;
+  } else if (nullptr != s->next_hop_strategy) {
+    // remap strategies do not support the TSHttpTxnParentProxySet API.
+    return s->parent_result.result == ParentResultType::SPECIFIED;
+  } else if (nullptr != s->parent_params) {
+    return s->parent_result.is_some();
+  }
+  return false;
+}
+
 // wrapper to choose between a remap next hop strategy or use parent.config
 // remap next hop strategy is preferred
 inline static bool
 is_api_result(HttpTransact::State *s)
 {
-  bool r = false;
+  bool res = false;
   if (nullptr != s->next_hop_strategy) {
     // remap strategies do not support the TSHttpTxnParentProxySet API.
-    r = false;
+    res = false;
   } else if (nullptr != s->parent_params) {
-    r = s->parent_result.is_api_result();
+    res = s->parent_result.is_api_result();
   }
-  return r;
+  return res;
 }
 
 // wrapper to get the max_retries.
@@ -207,6 +239,8 @@ retry_type(HttpTransact::State *s)
 inline static void
 findParent(HttpTransact::State *s)
 {
+  TxnDbg(dbg_ctl_http_trans, "findParent");
+
   Metrics::Counter::increment(http_rsb.parent_count);
   if (s->response_action.handled) {
     s->parent_result.hostname = s->response_action.action.hostname;
@@ -295,6 +329,8 @@ parentExists(HttpTransact::State *s)
 inline static void
 nextParent(HttpTransact::State *s)
 {
+  TxnDbg(dbg_ctl_http_trans, "nextParent");
+
   TxnDbg(dbg_ctl_parent_down, "connection to parent %s failed, conn_state: %s, 
request to origin: %s", s->parent_result.hostname,
          HttpDebugNames::get_server_state_name(s->current.state), 
s->request_data.get_host());
   Metrics::Counter::increment(http_rsb.parent_count);
@@ -599,10 +635,11 @@ find_server_and_update_current_info(HttpTransact::State 
*s)
     } else {
       findParent(s);
     }
-    if (!s->parent_result.is_some() || is_api_result(s) || parent_is_proxy(s)) 
{
+    if (!is_some(s) || is_api_result(s) || parent_is_proxy(s)) {
       TxnDbg(dbg_ctl_http_trans, "request not cacheable, so bypass parent");
       s->parent_result.result = ParentResultType::DIRECT;
     }
+
   } else if (s->txn_conf->uncacheable_requests_bypass_parent && 
s->txn_conf->no_dns_forward_to_parent == 0 &&
              !HttpTransact::is_request_cache_lookupable(s)) {
     // request not lookupable and cacheable, so bypass parent if the parent is 
not an origin server.
@@ -616,10 +653,12 @@ find_server_and_update_current_info(HttpTransact::State 
*s)
     } else {
       findParent(s);
     }
-    if (!s->parent_result.is_some() || is_api_result(s) || parent_is_proxy(s)) 
{
+
+    if (!is_some(s) || is_api_result(s) || parent_is_proxy(s)) {
       TxnDbg(dbg_ctl_http_trans, "request not cacheable, so bypass parent");
       s->parent_result.result = ParentResultType::DIRECT;
     }
+
   } else {
     switch (s->parent_result.result) {
     case ParentResultType::UNDEFINED:
@@ -660,14 +699,32 @@ find_server_and_update_current_info(HttpTransact::State 
*s)
   }
 
   switch (s->parent_result.result) {
-  case ParentResultType::SPECIFIED:
-    s->parent_info.name = s->arena.str_store(s->parent_result.hostname, 
strlen(s->parent_result.hostname));
+  case ParentResultType::SPECIFIED: {
+    char const *const hostname = s->parent_result.hostname;
+
+    if (nullptr != hostname) {
+      s->parent_info.name = s->arena.str_store(hostname, strlen(hostname));
+
+      // if host header override option enabled
+      if (host_override(s)) {
+        TxnDbg(dbg_ctl_http_trans, "overriding host header with parent %s", 
hostname);
+        if (!s->hdr_info.server_request.valid()) {
+          
s->hdr_info.client_request.value_set(static_cast<std::string_view>(MIME_FIELD_HOST),
 hostname);
+          s->hdr_info.client_request.mark_target_dirty();
+        } else {
+          
s->hdr_info.server_request.value_set(static_cast<std::string_view>(MIME_FIELD_HOST),
 hostname);
+          s->hdr_info.server_request.mark_target_dirty();
+        }
+      }
+    }
+
     update_current_info(&s->current, &s->parent_info, 
ResolveInfo::PARENT_PROXY, false);
     update_dns_info(&s->dns_info, &s->current);
     ink_assert(s->dns_info.looking_up == ResolveInfo::PARENT_PROXY);
     s->next_hop_scheme = URL_WKSIDX_HTTP;
 
     return ResolveInfo::PARENT_PROXY;
+  }
   case ParentResultType::FAIL:
     // No more parents - need to return an error message
     s->current.request_to = ResolveInfo::HOST_NONE;
diff --git a/src/proxy/http/remap/NextHopConsistentHash.cc 
b/src/proxy/http/remap/NextHopConsistentHash.cc
index 848e78a751..635e153c1e 100644
--- a/src/proxy/http/remap/NextHopConsistentHash.cc
+++ b/src/proxy/http/remap/NextHopConsistentHash.cc
@@ -547,8 +547,4 @@ NextHopConsistentHash::findNextHop(TSHttpTxn txnp, void * 
/* ih ATS_UNUSED */, t
     NH_Dbg(NH_DBG_CTL, "[%" PRIu64 "] result.result: %s set hostname null port 
0 retry false", sm_id,
            ParentResultStr[static_cast<int>(result.result)]);
   }
-
-  setHostHeader(txnp, result.hostname);
-
-  return;
 }
diff --git a/src/proxy/http/remap/NextHopRoundRobin.cc 
b/src/proxy/http/remap/NextHopRoundRobin.cc
index 3acb8411c3..c04acbdaa5 100644
--- a/src/proxy/http/remap/NextHopRoundRobin.cc
+++ b/src/proxy/http/remap/NextHopRoundRobin.cc
@@ -170,7 +170,6 @@ NextHopRoundRobin::findNextHop(TSHttpTxn txnp, void * /* ih 
ATS_UNUSED */, time_
       result->last_parent = cur_hst_index;
       result->last_group  = cur_grp_index;
       result->retry       = parentRetry;
-      setHostHeader(txnp, result->hostname);
       ink_assert(result->hostname != nullptr);
       ink_assert(result->port != 0);
       NH_Dbg(NH_DBG_CTL, "[%" PRIu64 "] Chosen parent = %s.%d", sm_id, 
result->hostname, result->port);
diff --git a/src/proxy/http/remap/NextHopSelectionStrategy.cc 
b/src/proxy/http/remap/NextHopSelectionStrategy.cc
index e308e73a4b..6ca4cf78ef 100644
--- a/src/proxy/http/remap/NextHopSelectionStrategy.cc
+++ b/src/proxy/http/remap/NextHopSelectionStrategy.cc
@@ -265,16 +265,6 @@ NextHopSelectionStrategy::markNextHop(TSHttpTxn txnp, 
const char *hostname, cons
   return passive_health.markNextHop(txnp, hostname, port, status, ih, now);
 }
 
-void
-NextHopSelectionStrategy::setHostHeader(TSHttpTxn txnp, const char *hostname)
-{
-  if (host_override && nullptr != hostname) {
-    HttpSM *sm = reinterpret_cast<HttpSM *>(txnp);
-    
sm->t_state.hdr_info.client_request.value_set(static_cast<std::string_view>(MIME_FIELD_HOST),
 hostname);
-    NH_Dbg(NH_DBG_CTL, "[%" PRIu64 "] overriding host header with parent %s", 
sm->sm_id, hostname);
-  }
-}
-
 bool
 NextHopSelectionStrategy::nextHopExists(TSHttpTxn txnp, void * /* ih 
ATS_UNUSED */)
 {
diff --git a/tests/gold_tests/tls/ssl/gen_foobar_certs.sh 
b/tests/gold_tests/tls/ssl/gen_foobar_certs.sh
new file mode 100755
index 0000000000..ad45e8d836
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/gen_foobar_certs.sh
@@ -0,0 +1,29 @@
+#  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.
+
+# bash script used to generate sni parent test certs
+
+for name in foo bar
+do
+  openssl req -x509 -newkey rsa:2048 \
+    -keyout server-${name}.key -out server-${name}.pem \
+    -days 3650 -nodes \
+    -subj "/C=US/ST=CO/L=Denver/O=Comcast/OU=Edge/CN=${name}.com" \
+    -addext 
"subjectAltName=DNS:${name}.com,DNS:www.${name}.com,DNS:*.${name}.com" \
+    -addext "basicConstraints=critical,CA:TRUE" \
+    -addext "keyUsage=critical,keyCertSign,cRLSign" \
+    -addext "subjectKeyIdentifier=hash"
+done
diff --git a/tests/gold_tests/tls/ssl/server-bar.key 
b/tests/gold_tests/tls/ssl/server-bar.key
new file mode 100644
index 0000000000..afe8e73254
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/server-bar.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQC7hSSKR+PDyaK9
+Qf5Wi2DoBedwTaIiHRVUOmjGMhb9bHH/xQ1xXbIlXhl9LB5z6KWZTLN/6tDbJMlk
+SF5CEPLXt0+XSwpx1ZZpBaxInKCZoiUbfazwb95y8evPYfOOmMR8tP/BKtygo0+U
+MbzjpIGZEdOkyWF6U4BtMJopC41JOxkrwvGG3DDBDNMui3qAgpmedylGhz7Lilpu
+XLc2cprtIBbo8jcQj4G0My5flZhvHmFe29L2YboLG2JtS8e39zOe+pfLb7T0JmDm
+UgxeJB7KH/d7BI90J1tYctVH2ky20oXQ2R1IC2bhsfuDEIo5OhPu56rINSmvXYDD
+RHL1nJH9AgMBAAECggEAJYzSVdxyeBzS+UoTR7pOU/gGsd7h5injwQOXQoT6RJIo
+O9r2R6rHByOSQBYLHRPLwWhyE13up8t0hp/VPC6PqnG7PuUYeNYX8fzgVIQExu8I
+tjoS3OzR4zudiGApePcPdZd7FW/jUUmSoG7bT8x75S6ELp3i5q986qCOZkS5bJ3Q
+Tr6LdgwYsdRMuwTBmHFym3obd8rVe1oQ4QY2M0mYAraGnFxiCNV+S+hDNsJ7na3F
+hbeBk+YpFKZ/dGDrKClhNbOmpSZmazihHaYoKrBc+fhJSzxVU5YfN+Ds7jsOptpa
+m0pr8Di3QnibkIUrjNiB3ewkIi+tyo0YO5Zzq406oQKBgQDxt4r/6hni0mBY3VrR
+UT74gzGihqxfXTLjq7Hiju9BC5qK9MaVRl+s5I6+phfSg4kyJSFsoWxcoKYFE/Km
+4xpk3sJlVIBwRBmx76h5B3eEpLayhoBR2TT0Cl5M8yhvz2p8CQO/MmGLP3qe/PqH
+32rBNLAs65e4ts2uKUJMC+AkrQKBgQDGmcM6vgRtq/DASFMMqpimSX+8fj9AGqLf
+mVcItXHx7pS1Aiq1G3PSA4efSryMjoleez3mfqFoPvTSP0ASIaQua9rAerfWfYaG
+/P8yAG7oddBb0SDe7f05VjY5jv3dS7sSDHpfaIa06xtRxd6HqUo7iPOir0FR6UzA
+iNkxKfR8kQKBgQCnhxxxjmDukfxw2soM9RB90P/fsxNY7RFONjuN2J7+J/qugEP6
+RdId1DMS867jGoNGG/H0hlTCRh2Ku26cOB6c9r8o185FAQ4GAyJy77foWPi+9vWM
+xMwsr9r33jeSduFIoj7UjyiICDEGbDN/ZFtrGQdZutdnEFuxb6shZcGt6QKBgFJj
+r65K7iNhVTsvxeRXUYSKsUdNSIgbhL4mKwkd3Ot1ApQlFfqULPRPKpBWvOnCqBJe
+Jkvc9LD+jSo7uyTKeAYaEGIRhvqgkJKnmmbv7xLY7Vtp4q0ZJhgHP++Y9pA7vpu6
+OXojLt8XOfoukCbPgFA6fHhdJEgK9SBapV/T++1BAoGBAMzO2jugm25fBzqWQpSe
+JV/CoQIbw2SUyXunsReIMqnYBETUDvzPrvQ0aRWUPtkzwf+vlbU7vauX4CRogB8e
+PWakmkCuSjPlxqycYUk/xIYEuaXn7Kk7IeF3UEbeSJLq9+cXeDun8OSE9lmD2Phn
+ITIVjf8kn1qEPgfr9GlGnaSN
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/tls/ssl/server-bar.pem 
b/tests/gold_tests/tls/ssl/server-bar.pem
new file mode 100644
index 0000000000..1a22b5935a
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/server-bar.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDnTCCAoWgAwIBAgIUS33d4I/Zq7YeC+bDnGr/e1BJkdwwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMQ8wDQYDVQQHDAZEZW52ZXIx
+EDAOBgNVBAoMB0NvbWNhc3QxDTALBgNVBAsMBEVkZ2UxEDAOBgNVBAMMB2Jhci5j
+b20wHhcNMjUxMjE3MTcxMDA0WhcNMzUxMjE1MTcxMDA0WjBeMQswCQYDVQQGEwJV
+UzELMAkGA1UECAwCQ08xDzANBgNVBAcMBkRlbnZlcjEQMA4GA1UECgwHQ29tY2Fz
+dDENMAsGA1UECwwERWRnZTEQMA4GA1UEAwwHYmFyLmNvbTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBALuFJIpH48PJor1B/laLYOgF53BNoiIdFVQ6aMYy
+Fv1scf/FDXFdsiVeGX0sHnPopZlMs3/q0NskyWRIXkIQ8te3T5dLCnHVlmkFrEic
+oJmiJRt9rPBv3nLx689h846YxHy0/8Eq3KCjT5QxvOOkgZkR06TJYXpTgG0wmikL
+jUk7GSvC8YbcMMEM0y6LeoCCmZ53KUaHPsuKWm5ctzZymu0gFujyNxCPgbQzLl+V
+mG8eYV7b0vZhugsbYm1Lx7f3M576l8tvtPQmYOZSDF4kHsof93sEj3QnW1hy1Ufa
+TLbShdDZHUgLZuGx+4MQijk6E+7nqsg1Ka9dgMNEcvWckf0CAwEAAaNTMFEwHQYD
+VR0OBBYEFPSlsbODTwZv1B8DmEyvQSzKWKQTMB8GA1UdIwQYMBaAFPSlsbODTwZv
+1B8DmEyvQSzKWKQTMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+ACOxraNGdHERXHVCW3ICKd14ASA+Xewc2pQrjc/VEAuOH10G1yyDzHCNZrmiJFYN
+IAje3JTfagKK6dFXgeC8tbJhHoNlC+Y/tLxdReuRshJdEraD/2ih2NgUqsI4soJT
+EhB+pUIeni5hF+ymWzewx3axrnblDEt5nl50zOnKKdHuDO6JlovnI50JaFdduWAS
+oOrornY+r6WO9CKt9uLmf/y4Epn6yFX7AL9aDzQQsS62+4o/nkQLf/u5eFRSLJK2
+Jj+QAWNP37is8q+2Jgmbt3DWybOq2znN1edNtUYjRCNafiK9qrhGeNp+yI/qB8dm
+W5pOyGNb+j9iY39KbNkSmTM=
+-----END CERTIFICATE-----
diff --git a/tests/gold_tests/tls/ssl/server-foo.key 
b/tests/gold_tests/tls/ssl/server-foo.key
new file mode 100644
index 0000000000..11d104b6e9
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/server-foo.key
@@ -0,0 +1,28 @@
+-----BEGIN PRIVATE KEY-----
+MIIEugIBADANBgkqhkiG9w0BAQEFAASCBKQwggSgAgEAAoIBAQCHQOoMuJlA4tSU
+DVSDcrb/ggBNvN4DIh3d4briYf2QFB1Gx7pnOwTG3Mt6jc6i/cBmOUhFWnYpe6An
+0on3ARlDzdXj3BiAd1Iw+BXCjxSGW6fVEWwalg09FkgOhRasYwOxggI01fGuY0uH
+CgRMi++4Xgr6umFFDh5hjvX1dLcn5gedseGLK+goiYI8ZPQIXWfIoKDAVb8aDFVu
++gIaZwexkQsSB/gr9uTqLNCZNlaI13fCuOFGLztvERDLwoQ88y1u9v9SpWnOB35l
+bbXf9jdcnv67ug0N04gtAYIUgyOOB0THDsjH2v8gKY7mL5UEOjzM0ZIKHGVDRSAp
+hgakHRVbAgMBAAECgf8h6GgJcWSImLm1168RQeQku9mcPYN75+izDBxMZvr67ZDX
+0p8Rrv+Oc6Wen5J/d2mJU5ol6mWVa90gG1IOeFBOHpvTn8Ki4Lc4oFVuJtobtSoE
+wKIwcy/r2YSFtEZRETZb1BZxkfNlS3cRdvMVbi6AVJrYzwkqTKHs01mgrf7zRs1G
+dpqSj32j4q6H/C6jF+3hBGfWX2HphNzEknZCjrtTqFDIdZ+v9XIAmthfAysKWLII
+oDcDj2NZbBgs32Woy90BTakxpE5r07spplXBdrEboF4ZikmZTd9GwMtY2pfzEMQ0
+IZzPyGyRyU8knr50mDeFNfSTcDdASUj5sBnYeQUCgYEAvQoDkGKm+DZNvI/ltTIF
+1cgcnb62Lrp+GHfSGNV4V0n3uQxyWLpFItARom5nfwrWoyAo+hskbqmAl2OcJj9J
+p3WIaudYb5NTzXYi4eY4gTOuyiXXsXR+PO1RvZEQnCPJAXH+Y+GxPVII5oxYcWeN
+mAnl5UrD17efMB3f2E8wDd8CgYEAtymmbkrxHMTry0JqR+r47i4eRS4LvXyPpVDa
+MMx8nHPqeIfYEgMjgmkBDZIHainKCoFsHseevLwTPz7UN4AXOqqYND4csZsjqAiI
+2/PEFiPczWiJkWN1JoUVBHgTdvrSaIEZ4xYsBfthfzgqan8Tkt/tDI9ysrWdC/QP
+EkbNMAUCgYA93bEc56xNPzhhpZY/nodaV4tF9Mwart3llBEmH3aq2oJABVrGvu8x
+XXbn+cnVQe7MpBHFCGz76m0zHl5UBhJMw6JJmjzPByA99ZOk6ntDjW5+qbPVV5KY
+zfIaYAdXkj6OQohGl+4xa0+OZA/tzT4Bq/uY9sbxKh7m2GARopjPcQKBgG1jboZI
+M5+e68PEsnypFGoLGQDv8wsDrTZqByVFutYlVE8PsjdvsHFeDMbyA4Of2Y5UpSHs
+zhyhpk0LVOqgkT70S1pIDhL0OGNOVY4nE2C1olT6rc4qu/h5WogEvns8aRUIpVE+
+GKSp8Rxtisd3hVUebxAlS7b3SAevhqC/sCORAoGAGy4yXxWrsNBu+OPpLDTiN3WN
+oou1MfVWx/5VmW71w5EUW1tZ1Umva6OSRszO8niRMARYvrqXMoOYRjHEaB1LoTiz
+berNI55iVxlfiwnl7AzigxU+EaDpf5wLPLi7yq5PJZSXW3TC5HZrqvVOEc4CAb1T
+7p7zNQCKEL3QA2jUymw=
+-----END PRIVATE KEY-----
diff --git a/tests/gold_tests/tls/ssl/server-foo.pem 
b/tests/gold_tests/tls/ssl/server-foo.pem
new file mode 100644
index 0000000000..ff9366f21b
--- /dev/null
+++ b/tests/gold_tests/tls/ssl/server-foo.pem
@@ -0,0 +1,22 @@
+-----BEGIN CERTIFICATE-----
+MIIDnTCCAoWgAwIBAgIUKNL6X8hvp7XV9J3DJg/IXI0XsawwDQYJKoZIhvcNAQEL
+BQAwXjELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAkNPMQ8wDQYDVQQHDAZEZW52ZXIx
+EDAOBgNVBAoMB0NvbWNhc3QxDTALBgNVBAsMBEVkZ2UxEDAOBgNVBAMMB2Zvby5j
+b20wHhcNMjUxMjE3MTcwOTU1WhcNMzUxMjE1MTcwOTU1WjBeMQswCQYDVQQGEwJV
+UzELMAkGA1UECAwCQ08xDzANBgNVBAcMBkRlbnZlcjEQMA4GA1UECgwHQ29tY2Fz
+dDENMAsGA1UECwwERWRnZTEQMA4GA1UEAwwHZm9vLmNvbTCCASIwDQYJKoZIhvcN
+AQEBBQADggEPADCCAQoCggEBAIdA6gy4mUDi1JQNVINytv+CAE283gMiHd3huuJh
+/ZAUHUbHumc7BMbcy3qNzqL9wGY5SEVadil7oCfSifcBGUPN1ePcGIB3UjD4FcKP
+FIZbp9URbBqWDT0WSA6FFqxjA7GCAjTV8a5jS4cKBEyL77heCvq6YUUOHmGO9fV0
+tyfmB52x4Ysr6CiJgjxk9AhdZ8igoMBVvxoMVW76AhpnB7GRCxIH+Cv25Oos0Jk2
+VojXd8K44UYvO28REMvChDzzLW72/1Klac4HfmVttd/2N1ye/ru6DQ3TiC0BghSD
+I44HRMcOyMfa/yApjuYvlQQ6PMzRkgocZUNFICmGBqQdFVsCAwEAAaNTMFEwHQYD
+VR0OBBYEFGKhV4jaRQ0wg1AWGgJleyIxP/UzMB8GA1UdIwQYMBaAFGKhV4jaRQ0w
+g1AWGgJleyIxP/UzMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggEB
+AEQ6IHWosM7rA0OTjTT8Cuovg1hO+EgFyXzzNYRUSNjlidXYPRfgXO1fRAK9Xzca
+dO+meaMgLxWK5AOcrtOLkorJom1tHzMsJmneRGiONGmGSNN84Fk0RwVWKz9Aumoj
+WVz+z/DEV+Myw8BQNP2BtZBX5MsDGWvLcZsZY9YNofIPvAVJONpiAuowc4/IQHH9
+156X3GYgaFkFEV9g6QqFpitR470DKihky8zpspJoeHD/pnZyY+3afKyU4i0YVPpa
+ZHri+ITdWjbqaRMewbcckIB6I+wWXTImHNibcL8sd1yLpw5+IDaV7AdMQmLj5Tmw
+6eiYbN3uwTKx79XwGOas1T0=
+-----END CERTIFICATE-----
diff --git a/tests/gold_tests/tls/tls_sni_parent_failover.test.py 
b/tests/gold_tests/tls/tls_sni_parent_failover.test.py
new file mode 100644
index 0000000000..ff64e168f5
--- /dev/null
+++ b/tests/gold_tests/tls/tls_sni_parent_failover.test.py
@@ -0,0 +1,174 @@
+#  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 parent failover with SNI name handling, validating that Traffic Server
+correctly fails over between HTTPS parents (including strategy-based parents)
+while enforcing TLS server name verification.
+'''
+
+# Define default ATS
+ts = Test.MakeATSProcess(
+    "ts",
+    enable_tls=True,
+    enable_cache=False,
+)
+
+server_foo = Test.MakeOriginServer(
+    "server_foo",
+    ssl=True,
+    options={
+        "--key": "{0}/server-foo.key".format(Test.RunDirectory),
+        "--cert": "{0}/server-foo.pem".format(Test.RunDirectory),
+    },
+)
+server_bar = Test.MakeOriginServer(
+    "server_bar",
+    ssl=True,
+    options={
+        "--key": "{0}/server-bar.key".format(Test.RunDirectory),
+        "--cert": "{0}/server-bar.pem".format(Test.RunDirectory),
+    },
+)
+
+# default check request/response
+request_foo_header = {"headers": "GET / HTTP/1.1\r\nHost: foo.com\r\n\r\n", 
"timestamp": "1469733493.993", "body": ""}
+response_foo_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: 
close\r\n\r\n", "timestamp": "1469733493.993", "body": "foo ok"}
+request_bar_header = {"headers": "GET / HTTP/1.1\r\nHost: bar.com\r\n\r\n", 
"timestamp": "1469733493.993", "body": ""}
+response_bar_header = {"headers": "HTTP/1.1 200 OK\r\nConnection: 
close\r\n\r\n", "timestamp": "1469733493.993", "body": "bar ok"}
+
+server_foo.addResponse("sessionlog.json", request_foo_header, 
response_foo_header)
+server_bar.addResponse("sessionlog.json", request_bar_header, 
response_bar_header)
+
+# successful request to be served by bar.com
+request_bar_header = {"headers": "GET /path HTTP/1.1\r\nHost: 
bar.com\r\n\r\n", "timestamp": "1469733493.993", "body": ""}
+response_bar_header = {
+    "headers": "HTTP/1.1 200 OK\r\nConnection: close\r\n\r\n",
+    "timestamp": "1469733493.993",
+    "body": "path bar ok"
+}
+
+server_bar.addResponse("sessionlog.json", request_bar_header, 
response_bar_header)
+
+ts.addSSLfile("ssl/server-foo.pem")
+ts.addSSLfile("ssl/server-foo.key")
+ts.addSSLfile("ssl/server-bar.pem")
+ts.addSSLfile("ssl/server-bar.key")
+
+dns = Test.MakeDNServer("dns")
+
+ts.Disk.records_config.update(
+    {
+        'proxy.config.diags.debug.enabled': 1,
+        'proxy.config.diags.debug.tags': 'http|ssl|parent_select|next_hop',
+        'proxy.config.ssl.client.verify.server.policy': 'ENFORCED',
+        'proxy.config.ssl.client.verify.server.properties': 'NAME',
+        'proxy.config.url_remap.pristine_host_hdr': 0,
+        'proxy.config.dns.nameservers': 
'127.0.0.1:{0}'.format(dns.Variables.Port),
+        'proxy.config.dns.resolv_conf': 'NULL',
+        'proxy.config.exec_thread.autoconfig.scale': 1.0,
+        'proxy.config.http.connect.down.policy': 1,  # tls failures don't mark 
down
+    })
+
+dns.addRecords(records={"foo.com.": ["127.0.0.1"]})
+dns.addRecords(records={"bar.com.": ["127.0.0.1"]})
+dns.addRecords(records={"parent.": ["127.0.0.1"]})
+dns.addRecords(records={"strategy.": ["127.0.0.1"]})
+
+ts.Disk.remap_config.AddLines(
+    [
+        "map http://parent https://parent";,
+        "map http://strategy https://strategy @strategy=strat",
+        "map http://parent_prist https://parent @plugin=conf_remap.so 
@pparam=proxy.config.url_remap.pristine_host_hdr=1",
+        "map http://strategy_prist https://strategy @strategy=strat 
@plugin=conf_remap.so @pparam=proxy.config.url_remap.pristine_host_hdr=1",
+    ])
+
+ts.Disk.parent_config.AddLine(
+    'dest_domain=. port=443 parent="foo.com:{0}|1;bar.com:{1}|1" 
parent_retry=simple_retry parent_is_proxy=false go_direct=false 
simple_server_retry_responses="404" host_override=true'
+    .format(server_foo.Variables.SSL_Port, server_bar.Variables.SSL_Port))
+
+# build strategies.yaml file
+ts.Disk.File(ts.Variables.CONFIGDIR + "/strategies.yaml", id="strategies", 
typename="ats:config")
+
+s = ts.Disk.strategies
+s.AddLine("groups:")
+s.AddLines(
+    [
+        f"  - &gstrat",
+        f"    - host: foo.com",
+        f"      protocol:",
+        f"      - scheme: https",
+        f"        port: {server_foo.Variables.SSL_Port}",
+        f"      weight: 1.0",
+        f"    - host: bar.com",
+        f"      protocol:",
+        f"      - scheme: https",
+        f"        port: {server_bar.Variables.SSL_Port}",
+        f"      weight: 1.0",
+    ])
+
+s.AddLine("strategies:")
+
+s.AddLines(
+    [
+        f"  - strategy: strat",
+        f"    policy: first_live",
+        f"    go_direct: false",
+        f"    parent_is_proxy: false",
+        f"    ignore_self_detect: true",
+        f"    host_override: true",
+        f"    groups:",
+        f"      - *gstrat",
+        f"    scheme: https",
+        f"    failover:",
+        f"      ring_mode: exhaust_ring",
+        f"      response_codes:",
+        f"        - 404",
+    ])
+
+curl_args = f"-s -L -o /dev/stdout -D /dev/stderr -x 
localhost:{ts.Variables.port} "
+
+tr = Test.AddTestRun("request with failover, parent.config")
+tr.Setup.Copy("ssl/server-foo.key")
+tr.Setup.Copy("ssl/server-foo.pem")
+tr.Setup.Copy("ssl/server-bar.key")
+tr.Setup.Copy("ssl/server-bar.pem")
+tr.MakeCurlCommand(curl_args + "http://parent/path";, ts=ts)
+tr.StillRunningAfter = ts
+ps = tr.Processes.Default
+ps.StartBefore(server_foo)
+ps.StartBefore(server_bar)
+ps.StartBefore(dns)
+ps.StartBefore(Test.Processes.ts)
+ps.Streams.stdout = Testers.ContainsExpression("path bar ok", "Expected 200 
response from bar.com")
+
+tr = Test.AddTestRun("request with failover, strategies.yaml")
+tr.MakeCurlCommand(curl_args + "http://strategy/path";, ts=ts)
+tr.StillRunningAfter = ts
+ps = tr.Processes.Default
+ps.Streams.stdout = Testers.ContainsExpression("path bar ok", "Expected 200 
response from bar.com")
+
+tr = Test.AddTestRun("request with failover, parent.config pristine_host_hdr")
+tr.MakeCurlCommand(curl_args + "http://parent_prist/path";, ts=ts)
+tr.StillRunningAfter = ts
+ps = tr.Processes.Default
+ps.Streams.stdout = Testers.ContainsExpression("path bar ok", "Expected 200 
response from bar.com")
+
+tr = Test.AddTestRun("request with failover, strategies.yaml")
+tr.MakeCurlCommand(curl_args + "http://strategy_prist/path";, ts=ts)
+tr.StillRunningAfter = ts
+ps = tr.Processes.Default
+ps.Streams.stdout = Testers.ContainsExpression("path bar ok", "Expected 200 
response from bar.com")

Reply via email to