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

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new c660866a1d proxy.config.ssl.client.CA.cert.filename: overridable 
(#13174)
c660866a1d is described below

commit c660866a1d2478df226e3e7645c47a4636f92e82
Author: Brian Neradt <[email protected]>
AuthorDate: Mon Jun 1 14:58:19 2026 -0500

    proxy.config.ssl.client.CA.cert.filename: overridable (#13174)
    
    This adds proxy.config.ssl.client.CA.cert.filename, the client CA
    certificate path, to the overridable config.
    
    Co-authored-by: bneradt <[email protected]>
---
 doc/admin-guide/files/records.yaml.en.rst          |  1 +
 .../api/functions/TSHttpOverridableConfig.en.rst   |  1 +
 .../api/types/TSOverridableConfigKey.en.rst        |  1 +
 include/cripts/Configs.hpp                         |  1 +
 include/proxy/http/HttpConfig.h                    |  2 ++
 include/proxy/http/OverridableConfigDefs.h         |  3 ++-
 include/ts/apidefs.h.in                            |  1 +
 src/api/InkAPI.cc                                  |  9 +++++++
 src/iocore/net/SSLNetVConnection.cc                | 30 +++++++++++++++++-----
 src/proxy/http/HttpSM.cc                           |  1 +
 src/proxy/http/PreWarmManager.cc                   |  1 +
 .../gold_tests/tls/tls_verify_ca_override.test.py  | 25 +++++++++++-------
 12 files changed, 59 insertions(+), 17 deletions(-)

diff --git a/doc/admin-guide/files/records.yaml.en.rst 
b/doc/admin-guide/files/records.yaml.en.rst
index b319c15b1b..9ea67b7ccd 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -4407,6 +4407,7 @@ Client-Related Configuration
 
 .. ts:cv:: CONFIG proxy.config.ssl.client.CA.cert.path STRING NULL
    :reloadable:
+   :overridable:
 
    Specifies the location of the certificate authority file against
    which the origin server will be verified.
diff --git a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst 
b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
index 9a175f4e15..623b40f3a1 100644
--- a/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
+++ b/doc/developer-guide/api/functions/TSHttpOverridableConfig.en.rst
@@ -190,6 +190,7 @@ TSOverridableConfigKey Value                                
              Config
 :enumerator:`TS_CONFIG_SSL_CLIENT_CERT_FILENAME`                        
:ts:cv:`proxy.config.ssl.client.cert.filename`
 :enumerator:`TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME`                 
:ts:cv:`proxy.config.ssl.client.private_key.filename`
 :enumerator:`TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME`                     
:ts:cv:`proxy.config.ssl.client.CA.cert.filename`
+:enumerator:`TS_CONFIG_SSL_CLIENT_CA_CERT_PATH`                         
:ts:cv:`proxy.config.ssl.client.CA.cert.path`
 :enumerator:`TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE`                 
:ts:cv:`proxy.config.hostdb.ip_resolve`
 :enumerator:`TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX`                  
:ts:cv:`proxy.config.plugin.vc.default_buffer_index`
 :enumerator:`TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK`             
:ts:cv:`proxy.config.plugin.vc.default_buffer_water_mark`
diff --git a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst 
b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
index 56d325e619..9d200845c6 100644
--- a/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
+++ b/doc/developer-guide/api/types/TSOverridableConfigKey.en.rst
@@ -154,6 +154,7 @@ Enumeration Members
 .. enumerator:: TS_CONFIG_SSL_CLIENT_SNI_POLICY
 .. enumerator:: TS_CONFIG_SSL_CLIENT_PRIVATE_KEY_FILENAME
 .. enumerator:: TS_CONFIG_SSL_CLIENT_CA_CERT_FILENAME
+.. enumerator:: TS_CONFIG_SSL_CLIENT_CA_CERT_PATH
 .. enumerator:: TS_CONFIG_HTTP_HOST_RESOLUTION_PREFERENCE
 .. enumerator:: TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_INDEX
 .. enumerator:: TS_CONFIG_PLUGIN_VC_DEFAULT_BUFFER_WATER_MARK
diff --git a/include/cripts/Configs.hpp b/include/cripts/Configs.hpp
index 6826f64247..c2f4597a6a 100644
--- a/include/cripts/Configs.hpp
+++ b/include/cripts/Configs.hpp
@@ -357,6 +357,7 @@ private:
           {
           public:
             cripts::StringConfig 
filename{"proxy.config.ssl.client.CA.cert.filename"};
+            cripts::StringConfig path{"proxy.config.ssl.client.CA.cert.path"};
           }; // End class Cert
 
         public:
diff --git a/include/proxy/http/HttpConfig.h b/include/proxy/http/HttpConfig.h
index 05a7cc511f..c5989c3620 100644
--- a/include/proxy/http/HttpConfig.h
+++ b/include/proxy/http/HttpConfig.h
@@ -773,6 +773,7 @@ struct OverridableHttpConfigParams {
   char *ssl_client_cert_filename        = nullptr;
   char *ssl_client_private_key_filename = nullptr;
   char *ssl_client_ca_cert_filename     = nullptr;
+  char *ssl_client_ca_cert_path         = nullptr;
   char *ssl_client_alpn_protocols       = nullptr;
 
   // Host Resolution order
@@ -1031,6 +1032,7 @@ inline HttpConfigParams::~HttpConfigParams()
   ats_free(oride.ssl_client_cert_filename);
   ats_free(oride.ssl_client_private_key_filename);
   ats_free(oride.ssl_client_ca_cert_filename);
+  ats_free(oride.ssl_client_ca_cert_path);
   ats_free(connect_ports_string);
   ats_free(reverse_proxy_no_host_redirect);
   ats_free(redirect_actions_string);
diff --git a/include/proxy/http/OverridableConfigDefs.h 
b/include/proxy/http/OverridableConfigDefs.h
index d70c4c54ca..a21e57d70a 100644
--- a/include/proxy/http/OverridableConfigDefs.h
+++ b/include/proxy/http/OverridableConfigDefs.h
@@ -250,6 +250,7 @@
   X(HTTP_CONNECT_ATTEMPTS_RETRY_BACKOFF_BASE,       
connect_attempts_retry_backoff_base,        
"proxy.config.http.connect_attempts_retry_backoff_base",          INT,    
GENERIC) \
   X(HTTP_NEGATIVE_REVALIDATING_LIST,                
negative_revalidating_list,                 
"proxy.config.http.negative_revalidating_list",                   STRING, 
HttpStatusCodeList_Conv) \
   X(HTTP_CACHE_POST_METHOD,                         cache_post_method,         
                 "proxy.config.http.cache.post_method",                         
   INT,    GENERIC) \
-  X(HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS,      
targeted_cache_control_headers,             
"proxy.config.http.cache.targeted_cache_control_headers",         STRING, 
TargetedCacheControlHeaders_Conv)
+  X(HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS,      
targeted_cache_control_headers,             
"proxy.config.http.cache.targeted_cache_control_headers",         STRING, 
TargetedCacheControlHeaders_Conv) \
+  X(SSL_CLIENT_CA_CERT_PATH,                        ssl_client_ca_cert_path,   
                 "proxy.config.ssl.client.CA.cert.path",                        
   STRING, NONE)
 
 // clang-format on
diff --git a/include/ts/apidefs.h.in b/include/ts/apidefs.h.in
index f458884779..bce145c8a4 100644
--- a/include/ts/apidefs.h.in
+++ b/include/ts/apidefs.h.in
@@ -916,6 +916,7 @@ enum TSOverridableConfigKey {
   TS_CONFIG_HTTP_NEGATIVE_REVALIDATING_LIST,
   TS_CONFIG_HTTP_CACHE_POST_METHOD,
   TS_CONFIG_HTTP_CACHE_TARGETED_CACHE_CONTROL_HEADERS,
+  TS_CONFIG_SSL_CLIENT_CA_CERT_PATH,
   TS_CONFIG_LAST_ENTRY,
 };
 
diff --git a/src/api/InkAPI.cc b/src/api/InkAPI.cc
index c5f80b6ef1..22ec85e8cb 100644
--- a/src/api/InkAPI.cc
+++ b/src/api/InkAPI.cc
@@ -7574,6 +7574,11 @@ TSHttpTxnConfigStringSet(TSHttpTxn txnp, 
TSOverridableConfigKey conf, const char
       s->t_state.my_txn_conf().ssl_client_ca_cert_filename = const_cast<char 
*>(value);
     }
     break;
+  case TS_CONFIG_SSL_CLIENT_CA_CERT_PATH:
+    if (value && length > 0) {
+      s->t_state.my_txn_conf().ssl_client_ca_cert_path = const_cast<char 
*>(value);
+    }
+    break;
   case TS_CONFIG_SSL_CLIENT_ALPN_PROTOCOLS:
     if (value && length > 0) {
       s->t_state.my_txn_conf().ssl_client_alpn_protocols = const_cast<char 
*>(value);
@@ -7653,6 +7658,10 @@ TSHttpTxnConfigStringGet(TSHttpTxn txnp, 
TSOverridableConfigKey conf, const char
     *value  = sm->t_state.txn_conf->server_session_sharing_match_str;
     *length = *value ? strlen(*value) : 0;
     break;
+  case TS_CONFIG_SSL_CLIENT_CA_CERT_PATH:
+    *value  = sm->t_state.txn_conf->ssl_client_ca_cert_path;
+    *length = *value ? strlen(*value) : 0;
+    break;
   default: {
     MgmtConverter const *conv;
     const void          *src = _conf_to_memberp(conf, sm->t_state.txn_conf, 
conv);
diff --git a/src/iocore/net/SSLNetVConnection.cc 
b/src/iocore/net/SSLNetVConnection.cc
index ab5eb9d32b..43f945176e 100644
--- a/src/iocore/net/SSLNetVConnection.cc
+++ b/src/iocore/net/SSLNetVConnection.cc
@@ -92,6 +92,17 @@ DbgCtl dbg_ctl_ssl_alpn{"ssl_alpn"};
 DbgCtl dbg_ctl_ssl_origin_session_cache{"ssl.origin_session_cache"};
 DbgCtl dbg_ctl_proxyprotocol{"proxyprotocol"};
 
+const char *
+resolve_client_ca_cert_path(const SSLConfigParams *params, const char *path, 
std::string &storage)
+{
+  if (path == nullptr) {
+    return params->clientCACertPath;
+  }
+
+  storage = Layout::get()->relative_to(Layout::get()->prefix, path);
+  return storage.c_str();
+}
+
 } // namespace
 
 //
@@ -1129,6 +1140,8 @@ SSLNetVConnection::_sslStartHandShake(int event, int &err)
       auto           nps       = sniParam->get_property_config(serverKey);
       shared_SSL_CTX sharedCTX = nullptr;
       SSL_CTX       *clientCTX = nullptr;
+      std::string    caCertPathStorage;
+      const char    *caCertPath = resolve_client_ca_cert_path(params, 
options.ssl_client_ca_cert_path, caCertPathStorage);
 
       // First Look to see if there are override parameters
       Dbg(dbg_ctl_ssl, "Checking for outbound client cert override [%p]", 
options.ssl_client_cert_name.get());
@@ -1144,18 +1157,21 @@ SSLNetVConnection::_sslStartHandShake(int event, int 
&err)
             keyFilePath = 
Layout::get()->relative_to(params->clientKeyPathOnly, 
options.ssl_client_private_key_name);
           }
           if (options.ssl_client_ca_cert_name) {
-            caCertFilePath = 
Layout::get()->relative_to(params->clientCACertPath, 
options.ssl_client_ca_cert_name);
+            caCertFilePath = Layout::get()->relative_to(caCertPath, 
options.ssl_client_ca_cert_name);
           }
           Dbg(dbg_ctl_ssl, "Using outbound client cert `%s'", 
options.ssl_client_cert_name.get());
         } else {
           Dbg(dbg_ctl_ssl, "Clearing outbound client cert");
         }
-        sharedCTX =
-          params->getCTX(certFilePath, keyFilePath, caCertFilePath.empty() ? 
params->clientCACertFilename : caCertFilePath.c_str(),
-                         params->clientCACertPath);
-      } else if (options.ssl_client_ca_cert_name) {
-        std::string caCertFilePath = 
Layout::get()->relative_to(params->clientCACertPath, 
options.ssl_client_ca_cert_name);
-        sharedCTX = params->getCTX(params->clientCertPath, 
params->clientKeyPath, caCertFilePath.c_str(), params->clientCACertPath);
+        sharedCTX = params->getCTX(certFilePath, keyFilePath,
+                                   caCertFilePath.empty() ? 
params->clientCACertFilename : caCertFilePath.c_str(), caCertPath);
+      } else if (options.ssl_client_ca_cert_name || 
options.ssl_client_ca_cert_path) {
+        std::string caCertFilePath;
+        if (options.ssl_client_ca_cert_name) {
+          caCertFilePath = Layout::get()->relative_to(caCertPath, 
options.ssl_client_ca_cert_name);
+        }
+        sharedCTX = params->getCTX(params->clientCertPath, 
params->clientKeyPath,
+                                   caCertFilePath.empty() ? 
params->clientCACertFilename : caCertFilePath.c_str(), caCertPath);
       } else if (nps && !nps->client_cert_file.empty()) {
         // If no overrides available, try the available nextHopProperty by 
reading from context mappings
         sharedCTX =
diff --git a/src/proxy/http/HttpSM.cc b/src/proxy/http/HttpSM.cc
index 7b69aaed8a..09c3a46c0e 100644
--- a/src/proxy/http/HttpSM.cc
+++ b/src/proxy/http/HttpSM.cc
@@ -5877,6 +5877,7 @@ HttpSM::do_http_server_open(bool raw, bool only_direct)
   opt.set_ssl_client_cert_name(t_state.txn_conf->ssl_client_cert_filename);
   opt.ssl_client_private_key_name = 
t_state.txn_conf->ssl_client_private_key_filename;
   opt.ssl_client_ca_cert_name     = 
t_state.txn_conf->ssl_client_ca_cert_filename;
+  opt.ssl_client_ca_cert_path     = t_state.txn_conf->ssl_client_ca_cert_path;
   if (is_private()) {
     // If the connection to origin is private, don't try to negotiate the 
higher overhead H2
     opt.alpn_protocols_array_size = -1;
diff --git a/src/proxy/http/PreWarmManager.cc b/src/proxy/http/PreWarmManager.cc
index 5fca803091..594fc24223 100644
--- a/src/proxy/http/PreWarmManager.cc
+++ b/src/proxy/http/PreWarmManager.cc
@@ -576,6 +576,7 @@ PreWarmSM::_connect(const IpEndpoint &addr)
     opt.ssl_client_cert_name        = 
http_conf_params->oride.ssl_client_cert_filename;
     opt.ssl_client_private_key_name = 
http_conf_params->oride.ssl_client_private_key_filename;
     opt.ssl_client_ca_cert_name     = 
http_conf_params->oride.ssl_client_ca_cert_filename;
+    opt.ssl_client_ca_cert_path     = 
http_conf_params->oride.ssl_client_ca_cert_path;
 
     SCOPED_MUTEX_LOCK(lock, mutex, this_ethread());
     connect_action_handle = sslNetProcessor.connect_re(this, &addr.sa, opt);
diff --git a/tests/gold_tests/tls/tls_verify_ca_override.test.py 
b/tests/gold_tests/tls/tls_verify_ca_override.test.py
index 610bc01603..1774f18150 100644
--- a/tests/gold_tests/tls/tls_verify_ca_override.test.py
+++ b/tests/gold_tests/tls/tls_verify_ca_override.test.py
@@ -17,7 +17,7 @@
 #  limitations under the License.
 
 Test.Summary = '''
-Test tls server  certificate verification options. Exercise conf_remap for ca 
bundle
+Test tls server certificate verification options. Exercise conf_remap for ca 
bundle path and file.
 '''
 
 # Define default ATS
@@ -58,18 +58,25 @@ ts.addSSLfile("ssl/signer.key")
 ts.addSSLfile("ssl/signer2.pem")
 ts.addSSLfile("ssl/signer2.key")
 
+
+def ca_cert_overrides(filename):
+    return (
+        f'@pparam=proxy.config.ssl.client.CA.cert.path={ts.Variables.SSLDir} '
+        f'@pparam=proxy.config.ssl.client.CA.cert.filename={filename}')
+
+
 ts.Disk.remap_config.AddLine(
-    'map /case1 https://127.0.0.1:{0}/ @plugin=conf_remap.so 
@pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(
-        server1.Variables.SSL_Port, ts.Variables.SSLDir, "signer.pem"))
+    f'map /case1 https://127.0.0.1:{server1.Variables.SSL_Port}/ '
+    f'@plugin=conf_remap.so {ca_cert_overrides("signer.pem")}')
 ts.Disk.remap_config.AddLine(
-    'map /badcase1 https://127.0.0.1:{0}/ @plugin=conf_remap.so 
@pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(
-        server1.Variables.SSL_Port, ts.Variables.SSLDir, "signer2.pem"))
+    f'map /badcase1 https://127.0.0.1:{server1.Variables.SSL_Port}/ '
+    f'@plugin=conf_remap.so {ca_cert_overrides("signer2.pem")}')
 ts.Disk.remap_config.AddLine(
-    'map /case2 https://127.0.0.1:{0}/ @plugin=conf_remap.so 
@pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(
-        server2.Variables.SSL_Port, ts.Variables.SSLDir, "signer2.pem"))
+    f'map /case2 https://127.0.0.1:{server2.Variables.SSL_Port}/ '
+    f'@plugin=conf_remap.so {ca_cert_overrides("signer2.pem")}')
 ts.Disk.remap_config.AddLine(
-    'map /badcase2 https://127.0.0.1:{0}/ @plugin=conf_remap.so 
@pparam=proxy.config.ssl.client.CA.cert.filename={1}/{2}'.format(
-        server2.Variables.SSL_Port, ts.Variables.SSLDir, "signer.pem"))
+    f'map /badcase2 https://127.0.0.1:{server2.Variables.SSL_Port}/ '
+    f'@plugin=conf_remap.so {ca_cert_overrides("signer.pem")}')
 
 ts.Disk.ssl_multicert_yaml.AddLines(
     """

Reply via email to