diff --git a/CHANGELOG.md b/CHANGELOG.md index 82f84f77272..5b07f63b032 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ Here is an overview of all new **experimental** features: - **Cassandra Scaler**: Add TLS support for cassandra scaler ([#5802](https://github.com/kedacore/keda/issues/5802)) - **GCP Scalers**: Added custom time horizon in GCP scalers ([#5778](https://github.com/kedacore/keda/issues/5778)) - **GitHub Scaler**: Fixed pagination, fetching repository list ([#5738](https://github.com/kedacore/keda/issues/5738)) +- **IBM MQ Scaler**: Add TLS support for IBM MQ scaler ([#5974](https://github.com/kedacore/keda/issues/5974)) - **Kafka**: Fix logic to scale to zero on invalid offset even with earliest offsetResetPolicy ([#5689](https://github.com/kedacore/keda/issues/5689)) - **MYSQL Scaler**: Add support to fetch username from env ([#5883](https://github.com/kedacore/keda/issues/5883)) diff --git a/pkg/scalers/ibmmq_scaler.go b/pkg/scalers/ibmmq_scaler.go index 052a6c08cbc..b9b52afc207 100644 --- a/pkg/scalers/ibmmq_scaler.go +++ b/pkg/scalers/ibmmq_scaler.go @@ -31,6 +31,7 @@ type IBMMQScaler struct { metricType v2.MetricTargetType metadata *IBMMQMetadata defaultHTTPTimeout time.Duration + httpClient *http.Client logger logr.Logger } @@ -45,6 +46,13 @@ type IBMMQMetadata struct { activationQueueDepth int64 tlsDisabled bool triggerIndex int + + // TLS + ca string + cert string + key string + keyPassword string + unsafeSsl bool } // CommandResponse Full structured response from MQ admin REST query @@ -75,16 +83,31 @@ func NewIBMMQScaler(config *scalersconfig.ScalerConfig) (Scaler, error) { return nil, fmt.Errorf("error parsing IBM MQ metadata: %w", err) } + httpClient := kedautil.CreateHTTPClient(config.GlobalHTTPTimeout, meta.unsafeSsl) + + // Configure TLS if cert and key are specified + if meta.cert != "" && meta.key != "" { + tlsConfig, err := kedautil.NewTLSConfigWithPassword(meta.cert, meta.key, meta.keyPassword, meta.ca, meta.unsafeSsl) + if err != nil { + return nil, err + } + httpClient.Transport = kedautil.CreateHTTPTransportWithTLSConfig(tlsConfig) + } + return &IBMMQScaler{ metricType: metricType, metadata: meta, defaultHTTPTimeout: config.GlobalHTTPTimeout, + httpClient: httpClient, logger: InitializeLogger(config, "ibm_mq_scaler"), }, nil } // Close closes and returns nil func (s *IBMMQScaler) Close(context.Context) error { + if s.httpClient != nil { + s.httpClient.CloseIdleConnections() + } return nil } @@ -144,24 +167,38 @@ func parseIBMMQMetadata(config *scalersconfig.ScalerConfig) (*IBMMQMetadata, err fmt.Println("No tls setting defined - setting default") meta.tlsDisabled = defaultTLSDisabled } - val, ok := config.AuthParams["username"] - switch { - case ok && val != "": + + if val, ok := config.AuthParams["username"]; ok && val != "" { meta.username = val - case config.TriggerMetadata["usernameFromEnv"] != "": - meta.username = config.ResolvedEnv[config.TriggerMetadata["usernameFromEnv"]] - default: + } else if val, ok := config.TriggerMetadata["usernameFromEnv"]; ok && val != "" { + meta.username = config.ResolvedEnv[val] + } else { return nil, fmt.Errorf("no username given") } - pwdValue, booleanValue := config.AuthParams["password"] // booleanValue reports whether the type assertion succeeded or not - switch { - case booleanValue && pwdValue != "": - meta.password = pwdValue - case config.TriggerMetadata["passwordFromEnv"] != "": - meta.password = config.ResolvedEnv[config.TriggerMetadata["passwordFromEnv"]] - default: + + if val, ok := config.AuthParams["password"]; ok && val != "" { + meta.password = val + } else if val, ok := config.TriggerMetadata["passwordFromEnv"]; ok && val != "" { + meta.password = config.ResolvedEnv[val] + } else { return nil, fmt.Errorf("no password given") } + + // TLS config (optional) + meta.ca = config.AuthParams["ca"] + meta.cert = config.AuthParams["cert"] + meta.key = config.AuthParams["key"] + meta.keyPassword = config.AuthParams["keyPassword"] + + meta.unsafeSsl = false + if val, ok := config.TriggerMetadata["unsafeSsl"]; ok { + boolVal, err := strconv.ParseBool(val) + if err != nil { + return nil, fmt.Errorf("failed to parse unsafeSsl value. Must be either true or false") + } + meta.unsafeSsl = boolVal + } + meta.triggerIndex = config.TriggerIndex return &meta, nil } @@ -178,11 +215,10 @@ func (s *IBMMQScaler) getQueueDepthViaHTTP(ctx context.Context) (int64, error) { } req.Header.Set("ibm-mq-rest-csrf-token", "value") req.Header.Set("Content-Type", "application/json") - req.SetBasicAuth(s.metadata.username, s.metadata.password) - client := kedautil.CreateHTTPClient(s.defaultHTTPTimeout, s.metadata.tlsDisabled) + req.SetBasicAuth(s.metadata.username, s.metadata.password) - resp, err := client.Do(req) + resp, err := s.httpClient.Do(req) if err != nil { return 0, fmt.Errorf("failed to contact MQ via REST: %w", err) } @@ -190,7 +226,7 @@ func (s *IBMMQScaler) getQueueDepthViaHTTP(ctx context.Context) (int64, error) { body, err := io.ReadAll(resp.Body) if err != nil { - return 0, fmt.Errorf("failed to ready body of request: %w", err) + return 0, fmt.Errorf("failed to read body of request: %w", err) } var response CommandResponse diff --git a/pkg/scalers/ibmmq_scaler_test.go b/pkg/scalers/ibmmq_scaler_test.go index 5eade446363..da5c12f4cdf 100644 --- a/pkg/scalers/ibmmq_scaler_test.go +++ b/pkg/scalers/ibmmq_scaler_test.go @@ -63,12 +63,16 @@ var testIBMMQMetadata = []parseIBMMQMetadataTestData{ {map[string]string{"host": testValidMQQueueURL, "queueManager": "testQueueManager", "queueDepth": "10"}, true, map[string]string{"username": "testUsername", "password": "Pass123"}}, // Invalid URL {map[string]string{"host": testInvalidMQQueueURL, "queueManager": "testQueueManager", "queueName": "testQueue", "queueDepth": "10"}, true, map[string]string{"username": "testUsername", "password": "Pass123"}}, - // Properly formed authParams + // Properly formed authParams Basic Auth {map[string]string{"host": testValidMQQueueURL, "queueManager": "testQueueManager", "queueName": "testQueue", "queueDepth": "10"}, false, map[string]string{"username": "testUsername", "password": "Pass123"}}, + // Properly formed authParams Basic Auth and TLS + {map[string]string{"host": testValidMQQueueURL, "queueManager": "testQueueManager", "queueName": "testQueue", "queueDepth": "10"}, false, map[string]string{"username": "testUsername", "password": "Pass123", "ca": "cavalue", "cert": "certvalue", "key": "keyvalue"}}, // No username provided {map[string]string{"host": testValidMQQueueURL, "queueManager": "testQueueManager", "queueName": "testQueue", "queueDepth": "10"}, true, map[string]string{"password": "Pass123"}}, // No password provided {map[string]string{"host": testValidMQQueueURL, "queueManager": "testQueueManager", "queueName": "testQueue", "queueDepth": "10"}, true, map[string]string{"username": "testUsername"}}, + // Wrong input unsafeSsl + {map[string]string{"host": testValidMQQueueURL, "queueManager": "testQueueManager", "queueName": "testQueue", "queueDepth": "10", "unsafeSsl": "random"}, true, map[string]string{"username": "testUsername", "password": "Pass123"}}, } // Test MQ Connection metadata is parsed correctly