diff --git a/pkg/apiclient/auth_jwt.go b/pkg/apiclient/auth_jwt.go index db3aea6d5a0..e81eb898edb 100644 --- a/pkg/apiclient/auth_jwt.go +++ b/pkg/apiclient/auth_jwt.go @@ -229,6 +229,16 @@ func (t *JWTTransport) RoundTrip(req *http.Request) (*http.Response, error) { t.ResetToken() } + // If we got a 401 and we're using mTLS, try to reload the certificate from disk. + // The cert may have been renewed while the old one expired in memory. + if resp.StatusCode == http.StatusUnauthorized { + if err := ReloadCertIfNeeded(t.Transport); err != nil { + log.Warnf("failed to reload client certificate: %s", err) + } else if CertPath != "" { + log.Infof("client certificate reloaded from %s", CertPath) + } + } + log.Debugf("retrying request to %s", req.URL.String()) attemptsCount[resp.StatusCode]++ diff --git a/pkg/apiclient/client.go b/pkg/apiclient/client.go index f9dd18aa980..c76df229b33 100644 --- a/pkg/apiclient/client.go +++ b/pkg/apiclient/client.go @@ -24,8 +24,38 @@ var ( Cert *tls.Certificate CaCertPool *x509.CertPool lapiClient *ApiClient + + // CertPath and KeyPath store the paths to the client certificate and key files. + // These are used to reload the certificate when it expires and is renewed on disk. + CertPath string + KeyPath string ) +// ReloadCertIfNeeded checks if mTLS is configured and reloads the client certificate +// from disk, updating the provided transport's TLS config. +// This is called when a 401 is received, as the cert may have been renewed on disk +// while the old one expired in memory. +func ReloadCertIfNeeded(transport http.RoundTripper) error { + // Only attempt reload if mTLS is configured with cert paths + if Cert == nil || CertPath == "" || KeyPath == "" { + return nil + } + + cert, err := tls.LoadX509KeyPair(CertPath, KeyPath) + if err != nil { + return fmt.Errorf("failed to reload certificate: %w", err) + } + + Cert = &cert + + // Update the transport's TLS config if possible + if httpTransport, ok := transport.(*http.Transport); ok && httpTransport.TLSClientConfig != nil { + httpTransport.TLSClientConfig.Certificates = []tls.Certificate{cert} + } + + return nil +} + type TokenSave func(ctx context.Context, token string) error type ApiClient struct { diff --git a/pkg/csconfig/api.go b/pkg/csconfig/api.go index 3c5f2365d0a..a363c49ffd3 100644 --- a/pkg/csconfig/api.go +++ b/pkg/csconfig/api.go @@ -216,6 +216,9 @@ func (l *LocalApiClientCfg) Load() error { } apiclient.Cert = &cert + // Store the paths so the certificate can be reloaded if it expires and is renewed on disk + apiclient.CertPath = l.Credentials.CertPath + apiclient.KeyPath = l.Credentials.KeyPath } return nil