From e6182b9f0da21939b20cd8976335624c4764caa5 Mon Sep 17 00:00:00 2001 From: stuart-wells Date: Thu, 9 Apr 2026 15:55:10 -0700 Subject: [PATCH 1/4] Supporting default TLS config for remote clusters Allowing customer configs to include a * section that defines a default rootCaFile for all undefined clusters. Initial Claude changes. --- .../rpc/encryption/fixedTLSConfigProvider.go | 6 +- .../encryption/local_store_tls_provider.go | 20 ++++- common/rpc/encryption/tls_config_test.go | 90 +++++++++++++++++++ 3 files changed, 114 insertions(+), 2 deletions(-) diff --git a/common/rpc/encryption/fixedTLSConfigProvider.go b/common/rpc/encryption/fixedTLSConfigProvider.go index adeb01a55a..d97c87219a 100644 --- a/common/rpc/encryption/fixedTLSConfigProvider.go +++ b/common/rpc/encryption/fixedTLSConfigProvider.go @@ -41,7 +41,11 @@ func (f *FixedTLSConfigProvider) GetFrontendClientConfig() (*tls.Config, error) // GetRemoteClusterClientConfig implements [TLSConfigProvider.GetRemoteClusterClientConfig]. func (f *FixedTLSConfigProvider) GetRemoteClusterClientConfig(hostname string) (*tls.Config, error) { - return f.RemoteClusterClientConfigs[hostname], nil + if cfg, ok := f.RemoteClusterClientConfigs[hostname]; ok { + return cfg, nil + } + // Fall back to default/wildcard config + return f.RemoteClusterClientConfigs[defaultRemoteCluster], nil } // GetExpiringCerts implements [TLSConfigProvider.GetExpiringCerts]. diff --git a/common/rpc/encryption/local_store_tls_provider.go b/common/rpc/encryption/local_store_tls_provider.go index dab35c79fa..cf70814e3e 100644 --- a/common/rpc/encryption/local_store_tls_provider.go +++ b/common/rpc/encryption/local_store_tls_provider.go @@ -48,6 +48,8 @@ type localStoreTlsProvider struct { var _ TLSConfigProvider = (*localStoreTlsProvider)(nil) var _ CertExpirationChecker = (*localStoreTlsProvider)(nil) +const defaultRemoteCluster = "*" + func NewLocalStoreTlsProvider(tlsConfig *config.RootTLS, metricsHandler metrics.Handler, logger log.Logger, certProviderFactory CertProviderFactory, ) (TLSConfigProvider, error) { @@ -141,7 +143,23 @@ func (s *localStoreTlsProvider) GetFrontendClientConfig() (*tls.Config, error) { func (s *localStoreTlsProvider) GetRemoteClusterClientConfig(hostname string) (*tls.Config, error) { groupTLS, ok := s.settings.RemoteClusters[hostname] if !ok { - return nil, nil + // Fall back to default/wildcard config if present + groupTLS, ok = s.settings.RemoteClusters[defaultRemoteCluster] + if !ok { + return nil, nil + } + return s.getOrCreateRemoteClusterClientConfig( + hostname, + func() (*tls.Config, error) { + return newClientTLSConfig( + s.remoteClusterClientCertProvider[defaultRemoteCluster], + groupTLS.Client.ServerName, + groupTLS.Server.RequireClientAuth, + false, + !groupTLS.Client.DisableHostVerification) + }, + groupTLS.IsClientEnabled(), + ) } return s.getOrCreateRemoteClusterClientConfig( diff --git a/common/rpc/encryption/tls_config_test.go b/common/rpc/encryption/tls_config_test.go index 12d728be96..7bbe04cdbd 100644 --- a/common/rpc/encryption/tls_config_test.go +++ b/common/rpc/encryption/tls_config_test.go @@ -1,11 +1,16 @@ package encryption import ( + "crypto/tls" + "crypto/x509" "testing" + "time" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "go.temporal.io/server/common/config" + "go.temporal.io/server/common/log" + "go.temporal.io/server/common/metrics" ) type ( @@ -218,3 +223,88 @@ func (s *tlsConfigTest) TestSystemWorkerTLSConfig() { client.RootCAData = []string{""} s.Error(validateRootTLS(cfg)) } + +// stubCertProvider is a no-op CertProvider for use in unit tests. +type stubCertProvider struct{} + +func (s *stubCertProvider) FetchServerCertificate() (*tls.Certificate, error) { return nil, nil } +func (s *stubCertProvider) FetchClientCAs() (*x509.CertPool, error) { return nil, nil } +func (s *stubCertProvider) FetchClientCertificate(_ bool) (*tls.Certificate, error) { + return nil, nil +} +func (s *stubCertProvider) FetchServerRootCAsForClient(_ bool) (*x509.CertPool, error) { + return nil, nil +} +func (s *stubCertProvider) GetExpiringCerts(_ time.Duration) (CertExpirationMap, CertExpirationMap, error) { + return nil, nil, nil +} + +func stubCertProviderFactory(_ *config.GroupTLS, _ *config.WorkerTLS, _ *config.ClientTLS, _ time.Duration, _ log.Logger) CertProvider { + return &stubCertProvider{} +} + +func newTestTLSProvider(t *testing.T, cfg config.RootTLS) TLSConfigProvider { + t.Helper() + provider, err := NewLocalStoreTlsProvider(&cfg, metrics.NoopMetricsHandler, log.NewTestLogger(), stubCertProviderFactory) + require.NoError(t, err) + return provider +} + +func TestGetRemoteClusterClientConfig_NoConfig(t *testing.T) { + provider := newTestTLSProvider(t, config.RootTLS{}) + tlsCfg, err := provider.GetRemoteClusterClientConfig("some-host") + require.NoError(t, err) + require.Nil(t, tlsCfg) +} + +func TestGetRemoteClusterClientConfig_ExactMatch(t *testing.T) { + cfg := config.RootTLS{ + RemoteClusters: map[string]config.GroupTLS{ + "cluster-a.example.com": {Client: config.ClientTLS{ForceTLS: true}}, + }, + } + provider := newTestTLSProvider(t, cfg) + + tlsCfg, err := provider.GetRemoteClusterClientConfig("cluster-a.example.com") + require.NoError(t, err) + require.NotNil(t, tlsCfg) + + // Unknown host with no default → nil + tlsCfg, err = provider.GetRemoteClusterClientConfig("cluster-b.example.com") + require.NoError(t, err) + require.Nil(t, tlsCfg) +} + +func TestGetRemoteClusterClientConfig_DefaultFallback(t *testing.T) { + cfg := config.RootTLS{ + RemoteClusters: map[string]config.GroupTLS{ + defaultRemoteCluster: {Client: config.ClientTLS{ForceTLS: true}}, + }, + } + provider := newTestTLSProvider(t, cfg) + + tlsCfg, err := provider.GetRemoteClusterClientConfig("any-unknown-host") + require.NoError(t, err) + require.NotNil(t, tlsCfg) +} + +func TestGetRemoteClusterClientConfig_ExactMatchTakesPriority(t *testing.T) { + cfg := config.RootTLS{ + RemoteClusters: map[string]config.GroupTLS{ + "cluster-a.example.com": {Client: config.ClientTLS{ForceTLS: true}}, + // Default has ForceTLS: false so IsClientEnabled() returns false → nil config + defaultRemoteCluster: {Client: config.ClientTLS{ForceTLS: false}}, + }, + } + provider := newTestTLSProvider(t, cfg) + + // Exact match → non-nil (ForceTLS: true) + tlsCfg, err := provider.GetRemoteClusterClientConfig("cluster-a.example.com") + require.NoError(t, err) + require.NotNil(t, tlsCfg) + + // Unknown host falls back to default (ForceTLS: false) → nil + tlsCfg, err = provider.GetRemoteClusterClientConfig("unknown-host") + require.NoError(t, err) + require.Nil(t, tlsCfg) +} From 5ceb49893831ef7def97c16a6a805e2c43f2a2ff Mon Sep 17 00:00:00 2001 From: stuart-wells Date: Thu, 9 Apr 2026 17:35:04 -0700 Subject: [PATCH 2/4] Adjusting tests and using default instead of * --- .../rpc/encryption/fixedTLSConfigProvider.go | 2 +- .../encryption/local_store_tls_provider.go | 2 +- common/rpc/encryption/tls_config_test.go | 25 ++++++++++++++----- 3 files changed, 21 insertions(+), 8 deletions(-) diff --git a/common/rpc/encryption/fixedTLSConfigProvider.go b/common/rpc/encryption/fixedTLSConfigProvider.go index d97c87219a..1d6819ff1c 100644 --- a/common/rpc/encryption/fixedTLSConfigProvider.go +++ b/common/rpc/encryption/fixedTLSConfigProvider.go @@ -44,7 +44,7 @@ func (f *FixedTLSConfigProvider) GetRemoteClusterClientConfig(hostname string) ( if cfg, ok := f.RemoteClusterClientConfigs[hostname]; ok { return cfg, nil } - // Fall back to default/wildcard config + // Fall back to default config return f.RemoteClusterClientConfigs[defaultRemoteCluster], nil } diff --git a/common/rpc/encryption/local_store_tls_provider.go b/common/rpc/encryption/local_store_tls_provider.go index cf70814e3e..c43a3eead8 100644 --- a/common/rpc/encryption/local_store_tls_provider.go +++ b/common/rpc/encryption/local_store_tls_provider.go @@ -48,7 +48,7 @@ type localStoreTlsProvider struct { var _ TLSConfigProvider = (*localStoreTlsProvider)(nil) var _ CertExpirationChecker = (*localStoreTlsProvider)(nil) -const defaultRemoteCluster = "*" +const defaultRemoteCluster = "default" func NewLocalStoreTlsProvider(tlsConfig *config.RootTLS, metricsHandler metrics.Handler, logger log.Logger, certProviderFactory CertProviderFactory, ) (TLSConfigProvider, error) { diff --git a/common/rpc/encryption/tls_config_test.go b/common/rpc/encryption/tls_config_test.go index 7bbe04cdbd..d0ea5d0e0b 100644 --- a/common/rpc/encryption/tls_config_test.go +++ b/common/rpc/encryption/tls_config_test.go @@ -257,6 +257,19 @@ func TestGetRemoteClusterClientConfig_NoConfig(t *testing.T) { require.Nil(t, tlsCfg) } +func TestGetRemoteClusterClientConfig_UnknownHostNoDefault(t *testing.T) { + cfg := config.RootTLS{ + RemoteClusters: map[string]config.GroupTLS{ + "cluster-a.example.com": {Client: config.ClientTLS{ForceTLS: true}}, + }, + } + provider := newTestTLSProvider(t, cfg) + + tlsCfg, err := provider.GetRemoteClusterClientConfig("unknown-host.example.com") + require.NoError(t, err) + require.Nil(t, tlsCfg) +} + func TestGetRemoteClusterClientConfig_ExactMatch(t *testing.T) { cfg := config.RootTLS{ RemoteClusters: map[string]config.GroupTLS{ @@ -291,20 +304,20 @@ func TestGetRemoteClusterClientConfig_DefaultFallback(t *testing.T) { func TestGetRemoteClusterClientConfig_ExactMatchTakesPriority(t *testing.T) { cfg := config.RootTLS{ RemoteClusters: map[string]config.GroupTLS{ - "cluster-a.example.com": {Client: config.ClientTLS{ForceTLS: true}}, + "cluster-a.example.com": {Client: config.ClientTLS{ForceTLS: false}}, // Default has ForceTLS: false so IsClientEnabled() returns false → nil config - defaultRemoteCluster: {Client: config.ClientTLS{ForceTLS: false}}, + defaultRemoteCluster: {Client: config.ClientTLS{ForceTLS: true}}, }, } provider := newTestTLSProvider(t, cfg) - // Exact match → non-nil (ForceTLS: true) + // Exact match → nil (ForceTLS: false) tlsCfg, err := provider.GetRemoteClusterClientConfig("cluster-a.example.com") require.NoError(t, err) - require.NotNil(t, tlsCfg) + require.Nil(t, tlsCfg) - // Unknown host falls back to default (ForceTLS: false) → nil + // Unknown host falls back to default (ForceTLS: true) → non-nil tlsCfg, err = provider.GetRemoteClusterClientConfig("unknown-host") require.NoError(t, err) - require.Nil(t, tlsCfg) + require.NotNil(t, tlsCfg) } From 7e3997a35e4e7b411a6d9529619ccd34e9e25099 Mon Sep 17 00:00:00 2001 From: stuart-wells Date: Tue, 14 Apr 2026 09:01:39 -0700 Subject: [PATCH 3/4] Linter fix --- common/rpc/encryption/tls_config_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/rpc/encryption/tls_config_test.go b/common/rpc/encryption/tls_config_test.go index d0ea5d0e0b..f2924781a1 100644 --- a/common/rpc/encryption/tls_config_test.go +++ b/common/rpc/encryption/tls_config_test.go @@ -235,7 +235,7 @@ func (s *stubCertProvider) FetchClientCertificate(_ bool) (*tls.Certificate, err func (s *stubCertProvider) FetchServerRootCAsForClient(_ bool) (*x509.CertPool, error) { return nil, nil } -func (s *stubCertProvider) GetExpiringCerts(_ time.Duration) (CertExpirationMap, CertExpirationMap, error) { +func (s *stubCertProvider) GetExpiringCerts(_ time.Duration) (expiring CertExpirationMap, expired CertExpirationMap, err error) { return nil, nil, nil } From df605a70a88c7bb069c36a11203571764fef4926 Mon Sep 17 00:00:00 2001 From: stuart-wells Date: Tue, 14 Apr 2026 10:11:30 -0700 Subject: [PATCH 4/4] Consolidation of code --- .../rpc/encryption/local_store_tls_provider.go | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/common/rpc/encryption/local_store_tls_provider.go b/common/rpc/encryption/local_store_tls_provider.go index c43a3eead8..65ff08ee05 100644 --- a/common/rpc/encryption/local_store_tls_provider.go +++ b/common/rpc/encryption/local_store_tls_provider.go @@ -141,6 +141,7 @@ func (s *localStoreTlsProvider) GetFrontendClientConfig() (*tls.Config, error) { } func (s *localStoreTlsProvider) GetRemoteClusterClientConfig(hostname string) (*tls.Config, error) { + certProviderKey := hostname groupTLS, ok := s.settings.RemoteClusters[hostname] if !ok { // Fall back to default/wildcard config if present @@ -148,25 +149,14 @@ func (s *localStoreTlsProvider) GetRemoteClusterClientConfig(hostname string) (* if !ok { return nil, nil } - return s.getOrCreateRemoteClusterClientConfig( - hostname, - func() (*tls.Config, error) { - return newClientTLSConfig( - s.remoteClusterClientCertProvider[defaultRemoteCluster], - groupTLS.Client.ServerName, - groupTLS.Server.RequireClientAuth, - false, - !groupTLS.Client.DisableHostVerification) - }, - groupTLS.IsClientEnabled(), - ) + certProviderKey = defaultRemoteCluster } return s.getOrCreateRemoteClusterClientConfig( hostname, func() (*tls.Config, error) { return newClientTLSConfig( - s.remoteClusterClientCertProvider[hostname], + s.remoteClusterClientCertProvider[certProviderKey], groupTLS.Client.ServerName, groupTLS.Server.RequireClientAuth, false,