diff --git a/Makefile b/Makefile index 5bca199..d3a3ab3 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ OUTPUT_PATH := $(OUTPUT_DIR)/$(BINARY_NAME) .PHONY: all build clean test test-verbose test-coverage test-race test-short test-clean benchmark test-auth test-crypto test-proxy test-utils -all: build +all: test build build: go build -o $(OUTPUT_PATH) $(CMD_DIR) diff --git a/cmd/main.go b/cmd/main.go index dce4589..72f2773 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -110,22 +110,22 @@ func main() { func configureProxy(proxyConns *proxy.Conn, cfg *utils.Config) error { ctx := context.TODO() - for _, t := range cfg.Targets { + for _, w := range cfg.Workloads { var authManager *auth.AuthManager var authType string - if t.Authentication != nil { + if w.Authentication != nil { authManager = auth.NewAuthManager() - authType = t.Authentication.Type + authType = w.Authentication.Type switch authType { case "spiffe": spiffeAuth := &auth.SpiffeAuthenticator{ - TrustDomain: t.Authentication.Config["trust_domain"].(string), - Endpoint: t.Authentication.Config["endpoint"].(string), + TrustDomain: w.Authentication.Config["trust_domain"].(string), + Endpoint: w.Authentication.Config["endpoint"].(string), } - if audiences, ok := t.Authentication.Config["audiences"].([]interface{}); ok { + if audiences, ok := w.Authentication.Config["audiences"].([]interface{}); ok { for _, a := range audiences { if audience, ok := a.(string); ok { spiffeAuth.Audiences = append(spiffeAuth.Audiences, audience) @@ -133,7 +133,7 @@ func configureProxy(proxyConns *proxy.Conn, cfg *utils.Config) error { } } - if err := spiffeAuth.Init(ctx, t.Authentication.Config); err != nil { + if err := spiffeAuth.Init(ctx, w.Authentication.Config); err != nil { return fmt.Errorf("failed to initialize spiffe authenticator: %w", err) } @@ -149,11 +149,11 @@ func configureProxy(proxyConns *proxy.Conn, cfg *utils.Config) error { metricsHandler := metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{ // Todo: do we need these many attributes? InitialAttributes: attribute.NewSet( - attribute.String("proxy-id", t.ProxyId), - attribute.String("target", t.Target), - attribute.String("namespace", t.Namespace), + attribute.String("workload_id", w.WorkloadId), + attribute.String("namespace", w.TemporalCloud.Namespace), + attribute.String("host_port", w.TemporalCloud.HostPort), attribute.String("auth_type", authType), - attribute.String("encryption_key", t.EncryptionKey), + attribute.String("encryption_key", w.EncryptionKey), ), }) @@ -172,12 +172,7 @@ func configureProxy(proxyConns *proxy.Conn, cfg *utils.Config) error { } err := proxyConns.AddConn(proxy.AddConnInput{ - ProxyId: t.ProxyId, - Target: t.Target, - TLSCertPath: t.TLS.CertFile, - TLSKeyPath: t.TLS.KeyFile, - EncryptionKeyID: t.EncryptionKey, - Namespace: t.Namespace, + Workload: &w, AuthManager: authManager, AuthType: authType, MetricsHandler: metricsHandler, diff --git a/config.yaml.sample b/config.yaml.sample index 9e2fcc9..83d0d84 100644 --- a/config.yaml.sample +++ b/config.yaml.sample @@ -1,21 +1,30 @@ server: port: 7233 host: "0.0.0.0" + metrics: port: 9090 + encryption: caching: max_cache: 100 max_age: "10m" max_usage: 100 -targets: - - proxy_id: "..internal" - target: "..tmprl.cloud:7233" - tls: - cert_file: "/path/to/./tls.crt" - key_file: "/path/to/./tls.key" + +workloads: + - workload_id: "" + temporal_cloud: + namespace: "." + host_port: "..tmprl.cloud:7233" # endpoint when using mTLS + # host_port: "..api.temporal.io:7233" # endpoint when using API keys + authentication: # only set either tls or api_key, not both + tls: + cert_file: "/path/to/./tls.crt" + key_file: "/path/to/./tls.key" + api_key: # only set either value or env, not both + value: "" + env: encryption_key: "" - namespace: "." authentication: type: "spiffe" config: @@ -24,13 +33,19 @@ targets: audiences: - "temporal_cloud_proxy" - - source: "..internal" - target: "..tmprl.cloud:7233" - tls: - cert_file: "/path/to/./tls.crt" - key_file: "/path/to/./tls.key" + - workload_id: "" + temporal_cloud: + namespace: "." + host_port: "..tmprl.cloud:7233" # endpoint when using mTLS + # host_port: "..api.temporal.io:7233" # endpoint when using API keys + authentication: # only set either tls or api_key, not both + tls: + cert_file: "/path/to/./tls.crt" + key_file: "/path/to/./tls.key" + api_key: # only set either value or env, not both + value: "" + env: encryption_key: "" - namespace: "." authentication: type: "spiffe" config: diff --git a/proxy/proxy.go b/proxy/proxy.go index 3b0fd13..0814be5 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -5,24 +5,22 @@ import ( "crypto/tls" "errors" "fmt" - "go.temporal.io/sdk/converter" - "os" - "sync" - "temporal-sa/temporal-cloud-proxy/codec" - "temporal-sa/temporal-cloud-proxy/crypto" - "github.com/aws/aws-sdk-go/aws" "github.com/aws/aws-sdk-go/aws/session" "github.com/aws/aws-sdk-go/service/kms" - - "temporal-sa/temporal-cloud-proxy/auth" - "temporal-sa/temporal-cloud-proxy/metrics" - + "go.temporal.io/sdk/converter" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" "google.golang.org/grpc/metadata" "google.golang.org/grpc/status" + "os" + "sync" + "temporal-sa/temporal-cloud-proxy/auth" + "temporal-sa/temporal-cloud-proxy/codec" + "temporal-sa/temporal-cloud-proxy/crypto" + "temporal-sa/temporal-cloud-proxy/metrics" + "temporal-sa/temporal-cloud-proxy/utils" ) type Conn struct { @@ -59,12 +57,7 @@ func createKMSClient() *kms.KMS { // AddConnInput contains parameters for adding a new connection type AddConnInput struct { - ProxyId string - Target string - TLSCertPath string - TLSKeyPath string - EncryptionKeyID string - Namespace string + Workload *utils.WorkloadConfig AuthManager *auth.AuthManager AuthType string MetricsHandler metrics.MetricsHandler @@ -73,18 +66,26 @@ type AddConnInput struct { // AddConn adds a new connection to the proxy func (mc *Conn) AddConn(input AddConnInput) error { - fmt.Printf("Adding connection id: %s target: %s\n", input.ProxyId, input.Target) + fmt.Printf("Adding connection id: %s namespace: %s hostport: %s\n", + input.Workload.WorkloadId, input.Workload.TemporalCloud.Namespace, input.Workload.TemporalCloud.HostPort) - cert, err := tls.LoadX509KeyPair(input.TLSCertPath, input.TLSKeyPath) - if err != nil { - return err + mc.mu.RLock() + _, exists := mc.namespace[input.Workload.WorkloadId] + mc.mu.RUnlock() + if exists { + return fmt.Errorf("workload-id %s already exists", input.Workload.WorkloadId) + } + + if input.Workload.TemporalCloud.Authentication.ApiKey != nil && input.Workload.TemporalCloud.Authentication.TLS != nil { + return fmt.Errorf("%s: cannot have both api key and mtls authentication configured on a single workload", + input.Workload.WorkloadId) } //Initialize AWS KMS client kmsClient := createKMSClient() codecContext := map[string]string{ - "namespace": input.Namespace, + "namespace": input.Workload.TemporalCloud.Namespace, } clientInterceptor, err := converter.NewPayloadCodecGRPCClientInterceptor( @@ -92,7 +93,7 @@ func (mc *Conn) AddConn(input AddConnInput) error { Codecs: []converter.PayloadCodec{codec.NewEncryptionCodecWithCaching( kmsClient, codecContext, - input.EncryptionKeyID, + input.Workload.EncryptionKey, input.MetricsHandler, input.CryptoCachingConfig, )}, @@ -102,21 +103,68 @@ func (mc *Conn) AddConn(input AddConnInput) error { return err } + tlsConfig := tls.Config{} + + grpcInterceptors := []grpc.UnaryClientInterceptor{ + clientInterceptor, + } + + if apiKeyConfig := input.Workload.TemporalCloud.Authentication.ApiKey; apiKeyConfig != nil { + if apiKeyConfig.Value != "" && apiKeyConfig.EnvVar != "" { + // TODO proper logging + fmt.Printf("WARN - multiple values provided for api key, using value. workload-id: %s\n", input.Workload.WorkloadId) + } + + apiKey := "" + if apiKeyConfig.Value != "" { + apiKey = apiKeyConfig.Value + } else if apiKeyConfig.EnvVar != "" { + apiKey = os.Getenv(apiKeyConfig.EnvVar) + } + + if apiKey == "" { + return fmt.Errorf("%s: no api key provided", input.Workload.WorkloadId) + } + + grpcInterceptors = append(grpcInterceptors, + func(ctx context.Context, method string, req any, reply any, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + md, ok := metadata.FromIncomingContext(ctx) + + if ok { + md = md.Copy() + md.Delete("authorization") + md.Delete("temporal-namespace") + + ctx = metadata.NewOutgoingContext(ctx, md) + ctx = metadata.AppendToOutgoingContext(ctx, "temporal-namespace", input.Workload.TemporalCloud.Namespace) + ctx = metadata.AppendToOutgoingContext(ctx, "authorization", "Bearer "+apiKey) + } + + return invoker(ctx, method, req, reply, cc, opts...) + }) + } else { + cert, err := tls.LoadX509KeyPair(input.Workload.TemporalCloud.Authentication.TLS.CertFile, + input.Workload.TemporalCloud.Authentication.TLS.KeyFile) + if err != nil { + return err + } + + tlsConfig.Certificates = []tls.Certificate{cert} + } + conn, err := grpc.NewClient( - input.Target, + input.Workload.TemporalCloud.HostPort, grpc.WithTransportCredentials(credentials.NewTLS( - &tls.Config{ - Certificates: []tls.Certificate{cert}, - }, + &tlsConfig, )), - grpc.WithUnaryInterceptor(clientInterceptor), + grpc.WithChainUnaryInterceptor(grpcInterceptors...), ) if err != nil { return err } mc.mu.Lock() - mc.namespace[input.ProxyId] = NamespaceConn{ + mc.namespace[input.Workload.WorkloadId] = NamespaceConn{ conn: conn, authManager: input.AuthManager, authType: input.AuthType, @@ -137,8 +185,10 @@ func (mc *Conn) CloseAll() error { if err := namespace.conn.Close(); err != nil { errs = append(errs, err) } - if err := namespace.authManager.Close(); err != nil { - errs = append(errs, err) + if namespace.authManager != nil { + if err := namespace.authManager.Close(); err != nil { + errs = append(errs, err) + } } } @@ -152,21 +202,21 @@ func (mc *Conn) Invoke(ctx context.Context, method string, args interface{}, rep return status.Errorf(codes.InvalidArgument, "unable to read metadata") } - proxyId := md.Get("proxy-id") + workloadId := md.Get("workload-id") - if len(proxyId) <= 0 { - return status.Error(codes.InvalidArgument, "metadata missing proxy-id") + if len(workloadId) <= 0 { + return status.Error(codes.InvalidArgument, "metadata missing workload-id") } - if len(proxyId) != 1 { - return status.Error(codes.InvalidArgument, "metadata contains multiple proxy-id entries") + if len(workloadId) != 1 { + return status.Error(codes.InvalidArgument, "metadata contains multiple workload-id entries") } mc.mu.RLock() - namespace, exists := mc.namespace[proxyId[0]] + namespace, exists := mc.namespace[workloadId[0]] mc.mu.RUnlock() if !exists { - return status.Errorf(codes.InvalidArgument, "invalid proxy-id: %s", proxyId[0]) + return status.Errorf(codes.InvalidArgument, "invalid workload-id: %s", workloadId[0]) } if namespace.authManager != nil { diff --git a/proxy/proxy_test.go b/proxy/proxy_test.go index 5f96621..1d22f7c 100644 --- a/proxy/proxy_test.go +++ b/proxy/proxy_test.go @@ -17,6 +17,8 @@ import ( "time" "temporal-sa/temporal-cloud-proxy/auth" + "temporal-sa/temporal-cloud-proxy/metrics" + "temporal-sa/temporal-cloud-proxy/utils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" @@ -116,60 +118,150 @@ func TestConn_AddConn(t *testing.T) { errorMsg string }{ { - name: "successful connection addition", + name: "successful connection addition with TLS", input: AddConnInput{ - ProxyId: "test-proxy-id", - Target: "localhost:7233", - TLSCertPath: certPath, - TLSKeyPath: keyPath, - EncryptionKeyID: "test-key-id", - Namespace: "test-namespace", - AuthManager: nil, // Use nil for simplicity in tests - AuthType: "jwt", + Workload: &utils.WorkloadConfig{ + WorkloadId: "test-workload-id", + TemporalCloud: utils.TemporalCloudConfig{ + Namespace: "test-namespace", + HostPort: "localhost:7233", + Authentication: utils.TemporalAuthConfig{ + TLS: &utils.TLSConfig{ + CertFile: certPath, + KeyFile: keyPath, + }, + }, + }, + EncryptionKey: "test-key-id", + }, + AuthManager: nil, // Use nil for simplicity in tests + AuthType: "jwt", + MetricsHandler: metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{}), + CryptoCachingConfig: nil, }, expectError: false, }, + { + name: "successful connection addition with API key (value)", + input: AddConnInput{ + Workload: &utils.WorkloadConfig{ + WorkloadId: "test-workload-id-api", + TemporalCloud: utils.TemporalCloudConfig{ + Namespace: "test-namespace", + HostPort: "localhost:7233", + Authentication: utils.TemporalAuthConfig{ + ApiKey: &utils.TemporalApiKeyConfig{ + Value: "test-api-key", + }, + }, + }, + EncryptionKey: "test-key-id", + }, + AuthManager: nil, + AuthType: "jwt", + MetricsHandler: metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{}), + CryptoCachingConfig: nil, + }, + expectError: false, + }, + { + name: "successful connection addition with API key (env var)", + input: AddConnInput{ + Workload: &utils.WorkloadConfig{ + WorkloadId: "test-workload-id-api-env", + TemporalCloud: utils.TemporalCloudConfig{ + Namespace: "test-namespace", + HostPort: "localhost:7233", + Authentication: utils.TemporalAuthConfig{ + ApiKey: &utils.TemporalApiKeyConfig{ + EnvVar: "TEST_TEMPORAL_API_KEY", + }, + }, + }, + EncryptionKey: "test-key-id", + }, + AuthManager: nil, + AuthType: "jwt", + MetricsHandler: metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{}), + CryptoCachingConfig: nil, + }, + expectError: true, // Will fail because env var is not set + }, { name: "invalid certificate path", input: AddConnInput{ - ProxyId: "test-proxy-id", - Target: "localhost:7233", - TLSCertPath: "/nonexistent/cert.pem", - TLSKeyPath: keyPath, - EncryptionKeyID: "test-key-id", - Namespace: "test-namespace", - AuthManager: nil, - AuthType: "jwt", + Workload: &utils.WorkloadConfig{ + WorkloadId: "test-workload-id", + TemporalCloud: utils.TemporalCloudConfig{ + Namespace: "test-namespace", + HostPort: "localhost:7233", + Authentication: utils.TemporalAuthConfig{ + TLS: &utils.TLSConfig{ + CertFile: "/nonexistent/cert.pem", + KeyFile: keyPath, + }, + }, + }, + EncryptionKey: "test-key-id", + }, + AuthManager: nil, + AuthType: "jwt", + MetricsHandler: metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{}), + CryptoCachingConfig: nil, }, expectError: true, }, { name: "invalid key path", input: AddConnInput{ - ProxyId: "test-proxy-id", - Target: "localhost:7233", - TLSCertPath: certPath, - TLSKeyPath: "/nonexistent/key.pem", - EncryptionKeyID: "test-key-id", - Namespace: "test-namespace", - AuthManager: nil, - AuthType: "jwt", + Workload: &utils.WorkloadConfig{ + WorkloadId: "test-workload-id", + TemporalCloud: utils.TemporalCloudConfig{ + Namespace: "test-namespace", + HostPort: "localhost:7233", + Authentication: utils.TemporalAuthConfig{ + TLS: &utils.TLSConfig{ + CertFile: certPath, + KeyFile: "/nonexistent/key.pem", + }, + }, + }, + EncryptionKey: "test-key-id", + }, + AuthManager: nil, + AuthType: "jwt", + MetricsHandler: metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{}), + CryptoCachingConfig: nil, }, expectError: true, }, { - name: "connection without auth manager", + name: "both API key and TLS configured - should error", input: AddConnInput{ - ProxyId: "test-proxy-id-no-auth", - Target: "localhost:7233", - TLSCertPath: certPath, - TLSKeyPath: keyPath, - EncryptionKeyID: "test-key-id", - Namespace: "test-namespace", - AuthManager: nil, - AuthType: "", + Workload: &utils.WorkloadConfig{ + WorkloadId: "test-workload-id", + TemporalCloud: utils.TemporalCloudConfig{ + Namespace: "test-namespace", + HostPort: "localhost:7233", + Authentication: utils.TemporalAuthConfig{ + ApiKey: &utils.TemporalApiKeyConfig{ + Value: "test-api-key", + }, + TLS: &utils.TLSConfig{ + CertFile: certPath, + KeyFile: keyPath, + }, + }, + }, + EncryptionKey: "test-key-id", + }, + AuthManager: nil, + AuthType: "jwt", + MetricsHandler: metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{}), + CryptoCachingConfig: nil, }, - expectError: false, + expectError: true, + errorMsg: "cannot have both api key and mtls authentication", }, } @@ -188,7 +280,7 @@ func TestConn_AddConn(t *testing.T) { assert.Equal(t, 1, len(conn.namespace)) // Verify the connection was stored correctly - nsConn, exists := conn.namespace[tt.input.ProxyId] + nsConn, exists := conn.namespace[tt.input.Workload.WorkloadId] assert.True(t, exists) assert.NotNil(t, nsConn.conn) assert.Equal(t, tt.input.AuthManager, nsConn.authManager) @@ -229,7 +321,7 @@ func TestConn_Invoke(t *testing.T) { expectedCode: codes.InvalidArgument, }, { - name: "missing proxy-id", + name: "missing workload-id", setupContext: func() context.Context { md := metadata.New(map[string]string{}) return metadata.NewIncomingContext(context.Background(), md) @@ -240,14 +332,14 @@ func TestConn_Invoke(t *testing.T) { method: "/test.Service/Method", expectError: true, expectedCode: codes.InvalidArgument, - errorContains: "metadata missing proxy-id", + errorContains: "metadata missing workload-id", }, { - name: "multiple proxy-id entries", + name: "multiple workload-id entries", setupContext: func() context.Context { md := metadata.New(map[string]string{}) - md.Append("proxy-id", "proxy-id-1") - md.Append("proxy-id", "proxy-id-2") + md.Append("workload-id", "workload-id-1") + md.Append("workload-id", "workload-id-2") return metadata.NewIncomingContext(context.Background(), md) }, setupConn: func() *Conn { @@ -256,13 +348,13 @@ func TestConn_Invoke(t *testing.T) { method: "/test.Service/Method", expectError: true, expectedCode: codes.InvalidArgument, - errorContains: "multiple proxy-id entries", + errorContains: "multiple workload-id entries", }, { - name: "target not found", + name: "workload not found", setupContext: func() context.Context { md := metadata.New(map[string]string{ - "proxy-id": "nonexistent-proxy-id", + "workload-id": "nonexistent-workload-id", }) return metadata.NewIncomingContext(context.Background(), md) }, @@ -272,19 +364,19 @@ func TestConn_Invoke(t *testing.T) { method: "/test.Service/Method", expectError: true, expectedCode: codes.InvalidArgument, - errorContains: "invalid proxy-id: nonexistent-proxy-id", + errorContains: "invalid workload-id: nonexistent-workload-id", }, { name: "invoke without authentication - skips auth logic", setupContext: func() context.Context { md := metadata.New(map[string]string{ - "proxy-id": "test-proxy-id-no-auth", + "workload-id": "test-workload-id-no-auth", }) return metadata.NewIncomingContext(context.Background(), md) }, setupConn: func() *Conn { conn := NewConn() - // Don't add any namespace connections to test the "target not found" path + // Don't add any namespace connections to test the "workload not found" path // This way we can test the logic without hitting the nil pointer return conn }, @@ -293,13 +385,13 @@ func TestConn_Invoke(t *testing.T) { reply: struct{}{}, expectError: true, expectedCode: codes.InvalidArgument, - errorContains: "invalid proxy-id: test-proxy-id-no-auth", + errorContains: "invalid workload-id: test-workload-id-no-auth", }, { name: "missing authorization with auth manager", setupContext: func() context.Context { md := metadata.New(map[string]string{ - "proxy-id": "test-proxy-id", + "workload-id": "test-workload-id", }) return metadata.NewIncomingContext(context.Background(), md) }, @@ -308,7 +400,7 @@ func TestConn_Invoke(t *testing.T) { // Create a real auth manager for testing authManager := auth.NewAuthManager() - conn.namespace["test-proxy-id"] = NamespaceConn{ + conn.namespace["test-workload-id"] = NamespaceConn{ conn: nil, authManager: authManager, authType: "jwt", @@ -324,7 +416,7 @@ func TestConn_Invoke(t *testing.T) { name: "multiple authorization entries", setupContext: func() context.Context { md := metadata.New(map[string]string{ - "proxy-id": "test-proxy-id", + "workload-id": "test-workload-id", }) md.Append("authorization", "Bearer token1") md.Append("authorization", "Bearer token2") @@ -335,7 +427,7 @@ func TestConn_Invoke(t *testing.T) { // Create a real auth manager for testing authManager := auth.NewAuthManager() - conn.namespace["test-proxy-id"] = NamespaceConn{ + conn.namespace["test-workload-id"] = NamespaceConn{ conn: nil, authManager: authManager, authType: "jwt", @@ -430,14 +522,24 @@ func TestConn_ConcurrentAccess(t *testing.T) { defer wg.Done() input := AddConnInput{ - ProxyId: fmt.Sprintf("proxy-id-%d", id), - Target: "localhost:7233", - TLSCertPath: certPath, - TLSKeyPath: keyPath, - EncryptionKeyID: "test-key-id", - Namespace: fmt.Sprintf("namespace-%d", id), - AuthManager: nil, - AuthType: "jwt", + Workload: &utils.WorkloadConfig{ + WorkloadId: fmt.Sprintf("workload-id-%d", id), + TemporalCloud: utils.TemporalCloudConfig{ + Namespace: fmt.Sprintf("namespace-%d", id), + HostPort: "localhost:7233", + Authentication: utils.TemporalAuthConfig{ + TLS: &utils.TLSConfig{ + CertFile: certPath, + KeyFile: keyPath, + }, + }, + }, + EncryptionKey: "test-key-id", + }, + AuthManager: nil, + AuthType: "jwt", + MetricsHandler: metrics.NewMetricsHandler(metrics.MetricsHandlerOptions{}), + CryptoCachingConfig: nil, } err := conn.AddConn(input) @@ -458,9 +560,9 @@ func TestConn_ConcurrentAccess(t *testing.T) { go func(id int) { defer wg.Done() - proxyId := id % numConnections + workloadId := id % numConnections md := metadata.New(map[string]string{ - "proxy-id": fmt.Sprintf("proxy-id-%d", proxyId), + "workload-id": fmt.Sprintf("workload-id-%d", workloadId), }) ctx := metadata.NewIncomingContext(context.Background(), md) @@ -508,7 +610,7 @@ func TestConn_InvokeWithAuthentication(t *testing.T) { name: "missing authorization header", setupContext: func() context.Context { md := metadata.New(map[string]string{ - "proxy-id": "test-proxy-id", + "workload-id": "test-workload-id", }) return metadata.NewIncomingContext(context.Background(), md) }, @@ -519,7 +621,7 @@ func TestConn_InvokeWithAuthentication(t *testing.T) { } // Add a namespace with auth manager (using nil since we can't easily mock the interface) - conn.namespace["test-proxy-id"] = NamespaceConn{ + conn.namespace["test-workload-id"] = NamespaceConn{ conn: nil, // Will cause failure, but we're testing auth logic first authManager: nil, // We'll set this to non-nil to trigger auth checks authType: "jwt", @@ -528,9 +630,9 @@ func TestConn_InvokeWithAuthentication(t *testing.T) { // Set authManager to non-nil to trigger the auth logic // Use a real auth manager since we can't easily mock the interface authManager := auth.NewAuthManager() - nsConn := conn.namespace["test-proxy-id"] + nsConn := conn.namespace["test-workload-id"] nsConn.authManager = authManager - conn.namespace["test-proxy-id"] = nsConn + conn.namespace["test-workload-id"] = nsConn for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/utils/config.go b/utils/config.go index 97c363d..c886075 100644 --- a/utils/config.go +++ b/utils/config.go @@ -4,7 +4,7 @@ type Config struct { Server ServerConfig `yaml:"server"` Metrics MetricsConfig `yaml:"metrics"` Encryption EncryptionConfig `yaml:"encryption"` - Targets []TargetConfig `yaml:"targets"` + Workloads []WorkloadConfig `yaml:"workloads"` } type ServerConfig struct { @@ -26,13 +26,27 @@ type CachingConfig struct { MaxUsage int `yaml:"max_usage,omitempty"` } -type TargetConfig struct { - ProxyId string `yaml:"proxy_id"` - Target string `yaml:"target"` - TLS TLSConfig `yaml:"tls"` - EncryptionKey string `yaml:"encryption_key"` - Namespace string `yaml:"namespace"` - Authentication *AuthConfig `yaml:"authentication,omitempty"` +type WorkloadConfig struct { + WorkloadId string `yaml:"workload_id"` + TemporalCloud TemporalCloudConfig `yaml:"temporal_cloud"` + EncryptionKey string `yaml:"encryption_key"` + Authentication *AuthConfig `yaml:"authentication,omitempty"` +} + +type TemporalCloudConfig struct { + Namespace string `yaml:"namespace"` + HostPort string `yaml:"host_port"` + Authentication TemporalAuthConfig `yaml:"authentication"` +} + +type TemporalAuthConfig struct { + TLS *TLSConfig `yaml:"tls,omitempty"` + ApiKey *TemporalApiKeyConfig `yaml:"api_key,omitempty"` +} + +type TemporalApiKeyConfig struct { + Value string `yaml:"value,omitempty"` + EnvVar string `yaml:"env,omitempty"` } type TLSConfig struct { diff --git a/utils/config_manager_test.go b/utils/config_manager_test.go index c5c644c..7b75730 100644 --- a/utils/config_manager_test.go +++ b/utils/config_manager_test.go @@ -23,14 +23,16 @@ func TestNewConfigManager(t *testing.T) { server: port: 7233 host: "0.0.0.0" -targets: - - proxy_id: "test.internal" - target: "test.external:7233" - tls: - cert_file: "/path/to/cert.crt" - key_file: "/path/to/key.key" +workloads: + - workload_id: "test.internal" + temporal_cloud: + namespace: "test-namespace" + host_port: "test.external:7233" + authentication: + tls: + cert_file: "/path/to/cert.crt" + key_file: "/path/to/key.key" encryption_key: "test-key" - namespace: "test-namespace" `, wantErr: false, expectNil: false, @@ -42,11 +44,11 @@ targets: server: port: 8080 host: "localhost" -targets: [] +workloads: [] `, wantErr: false, expectNil: false, - description: "should handle minimal config with empty targets", + description: "should handle minimal config with empty workloads", }, { name: "invalid yaml", @@ -134,21 +136,25 @@ func TestConfigManager_GetConfig(t *testing.T) { server: port: 9090 host: "127.0.0.1" -targets: - - proxy_id: "test1.internal" - target: "test1.external:9090" - tls: - cert_file: "/test1.crt" - key_file: "/test1.key" +workloads: + - workload_id: "test1.internal" + temporal_cloud: + namespace: "namespace1" + host_port: "test1.external:9090" + authentication: + tls: + cert_file: "/test1.crt" + key_file: "/test1.key" encryption_key: "key1" - namespace: "namespace1" - - proxy_id: "test2.internal" - target: "test2.external:9091" - tls: - cert_file: "/test2.crt" - key_file: "/test2.key" + - workload_id: "test2.internal" + temporal_cloud: + namespace: "namespace2" + host_port: "test2.external:9091" + authentication: + tls: + cert_file: "/test2.crt" + key_file: "/test2.key" encryption_key: "key2" - namespace: "namespace2" authentication: type: "spiffe" config: @@ -182,30 +188,30 @@ targets: t.Errorf("Expected server host to be '127.0.0.1', got %s", config.Server.Host) } - // Verify targets - if len(config.Targets) != 2 { - t.Errorf("Expected 2 targets, got %d", len(config.Targets)) + // Verify workloads + if len(config.Workloads) != 2 { + t.Errorf("Expected 2 workloads, got %d", len(config.Workloads)) } - if len(config.Targets) >= 1 { - target1 := config.Targets[0] - if target1.ProxyId != "test1.internal" { - t.Errorf("Expected first target proxy_id to be 'test1.internal', got %s", target1.ProxyId) + if len(config.Workloads) >= 1 { + workload1 := config.Workloads[0] + if workload1.WorkloadId != "test1.internal" { + t.Errorf("Expected first workload workload_id to be 'test1.internal', got %s", workload1.WorkloadId) } - if target1.Authentication != nil { - t.Error("Expected first target to have no authentication") + if workload1.Authentication != nil { + t.Error("Expected first workload to have no authentication") } } - if len(config.Targets) >= 2 { - target2 := config.Targets[1] - if target2.ProxyId != "test2.internal" { - t.Errorf("Expected second target proxy_id to be 'test2.internal', got %s", target2.ProxyId) + if len(config.Workloads) >= 2 { + workload2 := config.Workloads[1] + if workload2.WorkloadId != "test2.internal" { + t.Errorf("Expected second workload workload_id to be 'test2.internal', got %s", workload2.WorkloadId) } - if target2.Authentication == nil { - t.Error("Expected second target to have authentication") - } else if target2.Authentication.Type != "spiffe" { - t.Errorf("Expected second target auth type to be 'spiffe', got %s", target2.Authentication.Type) + if workload2.Authentication == nil { + t.Error("Expected second workload to have authentication") + } else if workload2.Authentication.Type != "spiffe" { + t.Errorf("Expected second workload auth type to be 'spiffe', got %s", workload2.Authentication.Type) } } } @@ -215,14 +221,16 @@ func TestConfigManager_GetConfig_ThreadSafety(t *testing.T) { server: port: 8080 host: "localhost" -targets: - - proxy_id: "concurrent.internal" - target: "concurrent.external:8080" - tls: - cert_file: "/concurrent.crt" - key_file: "/concurrent.key" +workloads: + - workload_id: "concurrent.internal" + temporal_cloud: + namespace: "concurrent-namespace" + host_port: "concurrent.external:8080" + authentication: + tls: + cert_file: "/concurrent.crt" + key_file: "/concurrent.key" encryption_key: "concurrent-key" - namespace: "concurrent-namespace" ` tmpDir := t.TempDir() @@ -259,11 +267,11 @@ targets: errors <- err return } - if len(config.Targets) != 1 { + if len(config.Workloads) != 1 { errors <- err return } - if config.Targets[0].ProxyId != "concurrent.internal" { + if config.Workloads[0].WorkloadId != "concurrent.internal" { errors <- err return } diff --git a/utils/config_test.go b/utils/config_test.go index 53e1f30..f955a73 100644 --- a/utils/config_test.go +++ b/utils/config_test.go @@ -14,19 +14,28 @@ func TestConfig_UnmarshalYAML(t *testing.T) { wantErr bool }{ { - name: "valid complete config", + name: "valid complete config with TLS authentication", yamlData: ` server: port: 7233 host: "0.0.0.0" -targets: - - proxy_id: "test.namespace.internal" - target: "test.namespace.tmprl.cloud:7233" - tls: - cert_file: "/path/to/cert.crt" - key_file: "/path/to/key.key" +metrics: + port: 8080 +encryption: + caching: + max_cache: 100 + max_age: "1h" + max_usage: 1000 +workloads: + - workload_id: "test.namespace.internal" + temporal_cloud: + namespace: "test.namespace" + host_port: "test.namespace.tmprl.cloud:7233" + authentication: + tls: + cert_file: "/path/to/cert.crt" + key_file: "/path/to/key.key" encryption_key: "test-key" - namespace: "test.namespace" authentication: type: "spiffe" config: @@ -40,16 +49,30 @@ targets: Port: 7233, Host: "0.0.0.0", }, - Targets: []TargetConfig{ + Metrics: MetricsConfig{ + Port: 8080, + }, + Encryption: EncryptionConfig{ + Caching: CachingConfig{ + MaxCache: 100, + MaxAge: "1h", + MaxUsage: 1000, + }, + }, + Workloads: []WorkloadConfig{ { - ProxyId: "test.namespace.internal", - Target: "test.namespace.tmprl.cloud:7233", - EncryptionKey: "test-key", - Namespace: "test.namespace", - TLS: TLSConfig{ - CertFile: "/path/to/cert.crt", - KeyFile: "/path/to/key.key", + WorkloadId: "test.namespace.internal", + TemporalCloud: TemporalCloudConfig{ + Namespace: "test.namespace", + HostPort: "test.namespace.tmprl.cloud:7233", + Authentication: TemporalAuthConfig{ + TLS: &TLSConfig{ + CertFile: "/path/to/cert.crt", + KeyFile: "/path/to/key.key", + }, + }, }, + EncryptionKey: "test-key", Authentication: &AuthConfig{ Type: "spiffe", Config: map[string]interface{}{ @@ -64,35 +87,47 @@ targets: wantErr: false, }, { - name: "minimal config without authentication", + name: "valid config with API key authentication (value)", yamlData: ` server: port: 8080 host: "localhost" -targets: - - proxy_id: "simple.internal" - target: "simple.external:8080" - tls: - cert_file: "/cert.crt" - key_file: "/key.key" +metrics: + port: 9090 +workloads: + - workload_id: "simple.internal" + temporal_cloud: + namespace: "simple" + host_port: "simple.external:8080" + authentication: + api_key: + value: "your-api-key-here" encryption_key: "simple-key" - namespace: "simple" `, want: Config{ Server: ServerConfig{ Port: 8080, Host: "localhost", }, - Targets: []TargetConfig{ + Metrics: MetricsConfig{ + Port: 9090, + }, + Encryption: EncryptionConfig{ + Caching: CachingConfig{}, + }, + Workloads: []WorkloadConfig{ { - ProxyId: "simple.internal", - Target: "simple.external:8080", - EncryptionKey: "simple-key", - Namespace: "simple", - TLS: TLSConfig{ - CertFile: "/cert.crt", - KeyFile: "/key.key", + WorkloadId: "simple.internal", + TemporalCloud: TemporalCloudConfig{ + Namespace: "simple", + HostPort: "simple.external:8080", + Authentication: TemporalAuthConfig{ + ApiKey: &TemporalApiKeyConfig{ + Value: "your-api-key-here", + }, + }, }, + EncryptionKey: "simple-key", Authentication: nil, }, }, @@ -100,26 +135,82 @@ targets: wantErr: false, }, { - name: "multiple targets", + name: "valid config with API key authentication (env var)", + yamlData: ` +server: + port: 8080 + host: "localhost" +metrics: + port: 9090 +workloads: + - workload_id: "simple.internal" + temporal_cloud: + namespace: "simple" + host_port: "simple.external:8080" + authentication: + api_key: + env: "TEMPORAL_API_KEY" + encryption_key: "simple-key" +`, + want: Config{ + Server: ServerConfig{ + Port: 8080, + Host: "localhost", + }, + Metrics: MetricsConfig{ + Port: 9090, + }, + Encryption: EncryptionConfig{ + Caching: CachingConfig{}, + }, + Workloads: []WorkloadConfig{ + { + WorkloadId: "simple.internal", + TemporalCloud: TemporalCloudConfig{ + Namespace: "simple", + HostPort: "simple.external:8080", + Authentication: TemporalAuthConfig{ + ApiKey: &TemporalApiKeyConfig{ + EnvVar: "TEMPORAL_API_KEY", + }, + }, + }, + EncryptionKey: "simple-key", + Authentication: nil, + }, + }, + }, + wantErr: false, + }, + { + name: "multiple workloads with mixed authentication", yamlData: ` server: port: 9090 host: "127.0.0.1" -targets: - - proxy_id: "target1.internal" - target: "target1.external:9090" - tls: - cert_file: "/target1.crt" - key_file: "/target1.key" +metrics: + port: 8081 +encryption: + caching: + max_cache: 50 +workloads: + - workload_id: "workload1.internal" + temporal_cloud: + namespace: "namespace1" + host_port: "workload1.external:9090" + authentication: + tls: + cert_file: "/workload1.crt" + key_file: "/workload1.key" encryption_key: "key1" - namespace: "namespace1" - - proxy_id: "target2.internal" - target: "target2.external:9091" - tls: - cert_file: "/target2.crt" - key_file: "/target2.key" + - workload_id: "workload2.internal" + temporal_cloud: + namespace: "namespace2" + host_port: "workload2.external:9091" + authentication: + api_key: + value: "workload2-api-key" encryption_key: "key2" - namespace: "namespace2" authentication: type: "oauth" config: @@ -131,27 +222,42 @@ targets: Port: 9090, Host: "127.0.0.1", }, - Targets: []TargetConfig{ + Metrics: MetricsConfig{ + Port: 8081, + }, + Encryption: EncryptionConfig{ + Caching: CachingConfig{ + MaxCache: 50, + }, + }, + Workloads: []WorkloadConfig{ { - ProxyId: "target1.internal", - Target: "target1.external:9090", - EncryptionKey: "key1", - Namespace: "namespace1", - TLS: TLSConfig{ - CertFile: "/target1.crt", - KeyFile: "/target1.key", + WorkloadId: "workload1.internal", + TemporalCloud: TemporalCloudConfig{ + Namespace: "namespace1", + HostPort: "workload1.external:9090", + Authentication: TemporalAuthConfig{ + TLS: &TLSConfig{ + CertFile: "/workload1.crt", + KeyFile: "/workload1.key", + }, + }, }, + EncryptionKey: "key1", Authentication: nil, }, { - ProxyId: "target2.internal", - Target: "target2.external:9091", - EncryptionKey: "key2", - Namespace: "namespace2", - TLS: TLSConfig{ - CertFile: "/target2.crt", - KeyFile: "/target2.key", + WorkloadId: "workload2.internal", + TemporalCloud: TemporalCloudConfig{ + Namespace: "namespace2", + HostPort: "workload2.external:9091", + Authentication: TemporalAuthConfig{ + ApiKey: &TemporalApiKeyConfig{ + Value: "workload2-api-key", + }, + }, }, + EncryptionKey: "key2", Authentication: &AuthConfig{ Type: "oauth", Config: map[string]interface{}{ @@ -242,16 +348,20 @@ func TestServerConfig_Validation(t *testing.T) { } } -func TestTargetConfig_Structure(t *testing.T) { - target := TargetConfig{ - ProxyId: "test.internal", - Target: "test.external:7233", - EncryptionKey: "test-key", - Namespace: "test-namespace", - TLS: TLSConfig{ - CertFile: "/path/to/cert.crt", - KeyFile: "/path/to/key.key", +func TestWorkloadConfig_Structure(t *testing.T) { + workload := WorkloadConfig{ + WorkloadId: "test.internal", + TemporalCloud: TemporalCloudConfig{ + Namespace: "test-namespace", + HostPort: "test.external:7233", + Authentication: TemporalAuthConfig{ + TLS: &TLSConfig{ + CertFile: "/path/to/cert.crt", + KeyFile: "/path/to/key.key", + }, + }, }, + EncryptionKey: "test-key", Authentication: &AuthConfig{ Type: "spiffe", Config: map[string]interface{}{ @@ -260,31 +370,31 @@ func TestTargetConfig_Structure(t *testing.T) { }, } - if target.ProxyId != "test.internal" { - t.Errorf("Expected ProxyId to be 'test.internal', got %s", target.ProxyId) + if workload.WorkloadId != "test.internal" { + t.Errorf("Expected WorkloadId to be 'test.internal', got %s", workload.WorkloadId) } - if target.Target != "test.external:7233" { - t.Errorf("Expected Target to be 'test.external:7233', got %s", target.Target) + if workload.TemporalCloud.HostPort != "test.external:7233" { + t.Errorf("Expected TemporalCloud.HostPort to be 'test.external:7233', got %s", workload.TemporalCloud.HostPort) } - if target.EncryptionKey != "test-key" { - t.Errorf("Expected EncryptionKey to be 'test-key', got %s", target.EncryptionKey) + if workload.EncryptionKey != "test-key" { + t.Errorf("Expected EncryptionKey to be 'test-key', got %s", workload.EncryptionKey) } - if target.Namespace != "test-namespace" { - t.Errorf("Expected Namespace to be 'test-namespace', got %s", target.Namespace) + if workload.TemporalCloud.Namespace != "test-namespace" { + t.Errorf("Expected TemporalCloud.Namespace to be 'test-namespace', got %s", workload.TemporalCloud.Namespace) } - if target.TLS.CertFile != "/path/to/cert.crt" { - t.Errorf("Expected TLS.CertFile to be '/path/to/cert.crt', got %s", target.TLS.CertFile) + if workload.TemporalCloud.Authentication.TLS.CertFile != "/path/to/cert.crt" { + t.Errorf("Expected TemporalCloud.Authentication.TLS.CertFile to be '/path/to/cert.crt', got %s", workload.TemporalCloud.Authentication.TLS.CertFile) } - if target.TLS.KeyFile != "/path/to/key.key" { - t.Errorf("Expected TLS.KeyFile to be '/path/to/key.key', got %s", target.TLS.KeyFile) + if workload.TemporalCloud.Authentication.TLS.KeyFile != "/path/to/key.key" { + t.Errorf("Expected TemporalCloud.Authentication.TLS.KeyFile to be '/path/to/key.key', got %s", workload.TemporalCloud.Authentication.TLS.KeyFile) } - if target.Authentication == nil { + if workload.Authentication == nil { t.Error("Expected Authentication to not be nil") } else { - if target.Authentication.Type != "spiffe" { - t.Errorf("Expected Authentication.Type to be 'spiffe', got %s", target.Authentication.Type) + if workload.Authentication.Type != "spiffe" { + t.Errorf("Expected Authentication.Type to be 'spiffe', got %s", workload.Authentication.Type) } - if trustDomain, ok := target.Authentication.Config["trust_domain"]; !ok || trustDomain != "spiffe://example.org/" { + if trustDomain, ok := workload.Authentication.Config["trust_domain"]; !ok || trustDomain != "spiffe://example.org/" { t.Errorf("Expected trust_domain to be 'spiffe://example.org/', got %v", trustDomain) } } @@ -342,19 +452,200 @@ func TestAuthConfig_Types(t *testing.T) { } } +func TestMetricsConfig_Structure(t *testing.T) { + tests := []struct { + name string + config MetricsConfig + want int + }{ + { + name: "default metrics port", + config: MetricsConfig{Port: 8080}, + want: 8080, + }, + { + name: "custom metrics port", + config: MetricsConfig{Port: 9090}, + want: 9090, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.config.Port != tt.want { + t.Errorf("Expected Port to be %d, got %d", tt.want, tt.config.Port) + } + }) + } +} + +func TestEncryptionConfig_Structure(t *testing.T) { + tests := []struct { + name string + config EncryptionConfig + want CachingConfig + }{ + { + name: "full caching config", + config: EncryptionConfig{ + Caching: CachingConfig{ + MaxCache: 100, + MaxAge: "1h", + MaxUsage: 1000, + }, + }, + want: CachingConfig{ + MaxCache: 100, + MaxAge: "1h", + MaxUsage: 1000, + }, + }, + { + name: "partial caching config", + config: EncryptionConfig{ + Caching: CachingConfig{ + MaxCache: 50, + }, + }, + want: CachingConfig{ + MaxCache: 50, + }, + }, + { + name: "empty caching config", + config: EncryptionConfig{ + Caching: CachingConfig{}, + }, + want: CachingConfig{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if tt.config.Caching.MaxCache != tt.want.MaxCache { + t.Errorf("Expected MaxCache to be %d, got %d", tt.want.MaxCache, tt.config.Caching.MaxCache) + } + if tt.config.Caching.MaxAge != tt.want.MaxAge { + t.Errorf("Expected MaxAge to be %s, got %s", tt.want.MaxAge, tt.config.Caching.MaxAge) + } + if tt.config.Caching.MaxUsage != tt.want.MaxUsage { + t.Errorf("Expected MaxUsage to be %d, got %d", tt.want.MaxUsage, tt.config.Caching.MaxUsage) + } + }) + } +} + +func TestTemporalAuthConfig_Structure(t *testing.T) { + tests := []struct { + name string + config TemporalAuthConfig + desc string + }{ + { + name: "TLS authentication", + config: TemporalAuthConfig{ + TLS: &TLSConfig{ + CertFile: "/path/to/cert.crt", + KeyFile: "/path/to/key.key", + }, + }, + desc: "should have TLS config and no API key", + }, + { + name: "API key authentication with value", + config: TemporalAuthConfig{ + ApiKey: &TemporalApiKeyConfig{ + Value: "test-api-key", + }, + }, + desc: "should have API key and no TLS config", + }, + { + name: "API key authentication with env var", + config: TemporalAuthConfig{ + ApiKey: &TemporalApiKeyConfig{ + EnvVar: "TEMPORAL_API_KEY", + }, + }, + desc: "should have API key env var and no TLS config", + }, + { + name: "empty authentication", + config: TemporalAuthConfig{}, + desc: "should have neither TLS nor API key", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + switch tt.name { + case "TLS authentication": + if tt.config.TLS == nil { + t.Error("Expected TLS to not be nil") + } else { + if tt.config.TLS.CertFile != "/path/to/cert.crt" { + t.Errorf("Expected CertFile to be '/path/to/cert.crt', got %s", tt.config.TLS.CertFile) + } + if tt.config.TLS.KeyFile != "/path/to/key.key" { + t.Errorf("Expected KeyFile to be '/path/to/key.key', got %s", tt.config.TLS.KeyFile) + } + } + if tt.config.ApiKey != nil { + t.Error("Expected ApiKey to be nil") + } + case "API key authentication with value": + if tt.config.ApiKey == nil { + t.Error("Expected ApiKey to not be nil") + } else { + if tt.config.ApiKey.Value != "test-api-key" { + t.Errorf("Expected ApiKey.Value to be 'test-api-key', got %s", tt.config.ApiKey.Value) + } + if tt.config.ApiKey.EnvVar != "" { + t.Errorf("Expected ApiKey.EnvVar to be empty, got %s", tt.config.ApiKey.EnvVar) + } + } + if tt.config.TLS != nil { + t.Error("Expected TLS to be nil") + } + case "API key authentication with env var": + if tt.config.ApiKey == nil { + t.Error("Expected ApiKey to not be nil") + } else { + if tt.config.ApiKey.EnvVar != "TEMPORAL_API_KEY" { + t.Errorf("Expected ApiKey.EnvVar to be 'TEMPORAL_API_KEY', got %s", tt.config.ApiKey.EnvVar) + } + if tt.config.ApiKey.Value != "" { + t.Errorf("Expected ApiKey.Value to be empty, got %s", tt.config.ApiKey.Value) + } + } + if tt.config.TLS != nil { + t.Error("Expected TLS to be nil") + } + case "empty authentication": + if tt.config.TLS != nil { + t.Error("Expected TLS to be nil") + } + if tt.config.ApiKey != nil { + t.Error("Expected ApiKey to be nil") + } + } + }) + } +} + // Helper function to compare Config structs func configEqual(a, b Config) bool { if a.Server.Port != b.Server.Port || a.Server.Host != b.Server.Host { return false } - if len(a.Targets) != len(b.Targets) { + if len(a.Workloads) != len(b.Workloads) { return false } - for i, targetA := range a.Targets { - targetB := b.Targets[i] - if !targetConfigEqual(targetA, targetB) { + for i, workloadA := range a.Workloads { + workloadB := b.Workloads[i] + if !workloadConfigEqual(workloadA, workloadB) { return false } } @@ -362,15 +653,41 @@ func configEqual(a, b Config) bool { return true } -func targetConfigEqual(a, b TargetConfig) bool { - if a.ProxyId != b.ProxyId || a.Target != b.Target || a.EncryptionKey != b.EncryptionKey || a.Namespace != b.Namespace { +func workloadConfigEqual(a, b WorkloadConfig) bool { + if a.WorkloadId != b.WorkloadId || a.EncryptionKey != b.EncryptionKey { + return false + } + + // Compare TemporalCloud configuration + if a.TemporalCloud.Namespace != b.TemporalCloud.Namespace || a.TemporalCloud.HostPort != b.TemporalCloud.HostPort { + return false + } + + // Compare TemporalCloud Authentication - API Key + if (a.TemporalCloud.Authentication.ApiKey == nil) != (b.TemporalCloud.Authentication.ApiKey == nil) { return false } - if a.TLS.CertFile != b.TLS.CertFile || a.TLS.KeyFile != b.TLS.KeyFile { + if a.TemporalCloud.Authentication.ApiKey != nil && b.TemporalCloud.Authentication.ApiKey != nil { + if a.TemporalCloud.Authentication.ApiKey.Value != b.TemporalCloud.Authentication.ApiKey.Value || + a.TemporalCloud.Authentication.ApiKey.EnvVar != b.TemporalCloud.Authentication.ApiKey.EnvVar { + return false + } + } + + // Compare TLS configuration + if (a.TemporalCloud.Authentication.TLS == nil) != (b.TemporalCloud.Authentication.TLS == nil) { return false } + if a.TemporalCloud.Authentication.TLS != nil && b.TemporalCloud.Authentication.TLS != nil { + if a.TemporalCloud.Authentication.TLS.CertFile != b.TemporalCloud.Authentication.TLS.CertFile || + a.TemporalCloud.Authentication.TLS.KeyFile != b.TemporalCloud.Authentication.TLS.KeyFile { + return false + } + } + + // Compare proxy Authentication (spiffe, oauth, etc.) if (a.Authentication == nil) != (b.Authentication == nil) { return false }