From 3393f84a792920e264cdeb6c81c420ceb9bdaf2d Mon Sep 17 00:00:00 2001 From: Seena Fallah Date: Fri, 14 Nov 2025 20:22:59 +0100 Subject: [PATCH] httpclient: add support for tls.Config.GetClientCertificate Expose tls.Config.GetClientCertificate to allow supplying a callback that loads the client certificate and key on each request. This enables mTLS setups where certificates are rotated automatically without restarting the application. Inspired by the etcd approach: https://github.com/etcd-io/etcd/pull/7829 Signed-off-by: Seena Fallah --- backend/http_settings.go | 18 ++++++++++++++++++ backend/httpclient/http_client.go | 4 ++++ backend/httpclient/options.go | 4 ++++ 3 files changed, 26 insertions(+) diff --git a/backend/http_settings.go b/backend/http_settings.go index 4b7641b7b..d43b1a1bc 100644 --- a/backend/http_settings.go +++ b/backend/http_settings.go @@ -1,6 +1,7 @@ package backend import ( + "crypto/tls" "encoding/json" "fmt" "net/http" @@ -36,6 +37,8 @@ type HTTPSettings struct { TLSCACert string TLSClientCert string TLSClientKey string + TLSClientCertFile string + TLSClientKeyFile string SigV4Auth bool SigV4Region string @@ -86,6 +89,15 @@ func (s *HTTPSettings) HTTPClientOptions() httpclient.Options { InsecureSkipVerify: s.TLSSkipVerify, ServerName: s.TLSServerName, } + if s.TLSClientCertFile != "" && s.TLSClientKeyFile != "" { + opts.TLS.GetClientCertificate = func(*tls.CertificateRequestInfo) (*tls.Certificate, error) { + cert, err := tls.LoadX509KeyPair(s.TLSClientCertFile, s.TLSClientKeyFile) + if err != nil { + return nil, fmt.Errorf("failed to load X509 key pair: %w", err) + } + return &cert, nil + } + } } if s.SigV4Auth { @@ -236,6 +248,12 @@ func parseHTTPSettings(jsonData json.RawMessage, secureJSONData map[string]strin if v, exists := dat["serverName"]; exists { s.TLSServerName = v.(string) } + if v, exists := dat["tlsClientCertFile"]; exists { + s.TLSClientCertFile = v.(string) + } + if v, exists := dat["tlsClientKeyFile"]; exists { + s.TLSClientKeyFile = v.(string) + } if v, exists := secureJSONData["tlsCACert"]; exists { s.TLSCACert = v } diff --git a/backend/httpclient/http_client.go b/backend/httpclient/http_client.go index 066c68913..1ddba5865 100644 --- a/backend/httpclient/http_client.go +++ b/backend/httpclient/http_client.go @@ -153,6 +153,10 @@ func GetTLSConfig(opts ...Options) (*tls.Config, error) { config.Certificates = []tls.Certificate{cert} } + if tlsOpts.GetClientCertificate != nil { + config.GetClientCertificate = tlsOpts.GetClientCertificate + } + if tlsOpts.MinVersion > 0 { config.MinVersion = tlsOpts.MinVersion } diff --git a/backend/httpclient/options.go b/backend/httpclient/options.go index 12898edae..581084807 100644 --- a/backend/httpclient/options.go +++ b/backend/httpclient/options.go @@ -118,6 +118,10 @@ type TLSOptions struct { // MaxVersion configures the tls.Config.MaxVersion. MaxVersion uint16 + + // GetClientCertificate optionally provides a callback + // for getting client certificates. + GetClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error) } // SigV4Config AWS SigV4 options.