Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tls authentication for redis scaler #4979

Merged
merged 2 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ To learn more about active deprecations, we recommend checking [GitHub Discussio
- **Azure Pod Identity**: Introduce validation to prevent usage of empty identity ID for Azure identity providers ([#4528](https://github.com/kedacore/keda/issues/4528))
- **Prometheus Scaler**: Remove trailing whitespaces in customAuthHeader and customAuthValue ([#4960](https://github.com/kedacore/keda/issues/4960))
- **Pulsar Scaler**: Add support for OAuth extensions ([#4700](https://github.com/kedacore/keda/issues/4700))
- **Redis Scalers**: Add TLS authentication support for Redis and Redis stream scalers ([#4917](https://github.com/kedacore/keda/issues/4917))

### Fixes
- **RabbitMQ Scaler**: Allow subpaths along with vhost in connection string ([#2634](https://github.com/kedacore/keda/issues/2634))
Expand Down
95 changes: 77 additions & 18 deletions pkg/scalers/redis_scaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ type redisConnectionInfo struct {
ports []string
enableTLS bool
unsafeSsl bool
cert string
key string
keyPassword string
ca string
}

type redisMetadata struct {
Expand Down Expand Up @@ -188,31 +192,74 @@ func createRedisScalerWithClient(client *redis.Client, meta *redisMetadata, scri
}
}

func parseRedisMetadata(config *ScalerConfig, parserFn redisAddressParser) (*redisMetadata, error) {
connInfo, err := parserFn(config.TriggerMetadata, config.ResolvedEnv, config.AuthParams)
if err != nil {
return nil, err
}
meta := redisMetadata{
connectionInfo: connInfo,
}

meta.connectionInfo.enableTLS = defaultEnableTLS
func parseTLSConfigIntoConnectionInfo(config *ScalerConfig, connInfo *redisConnectionInfo) error {
enableTLS := defaultEnableTLS
if val, ok := config.TriggerMetadata["enableTLS"]; ok {
tls, err := strconv.ParseBool(val)
if err != nil {
return nil, fmt.Errorf("enableTLS parsing error %w", err)
return fmt.Errorf("enableTLS parsing error %w", err)
}
meta.connectionInfo.enableTLS = tls
enableTLS = tls
}

meta.connectionInfo.unsafeSsl = false
connInfo.unsafeSsl = false
if val, ok := config.TriggerMetadata["unsafeSsl"]; ok {
parsedVal, err := strconv.ParseBool(val)
if err != nil {
return nil, fmt.Errorf("error parsing unsafeSsl: %w", err)
return fmt.Errorf("error parsing unsafeSsl: %w", err)
}
meta.connectionInfo.unsafeSsl = parsedVal
connInfo.unsafeSsl = parsedVal
}

// parse tls config defined in auth params
if val, ok := config.AuthParams["tls"]; ok {
val = strings.TrimSpace(val)
if enableTLS {
return errors.New("unable to set `tls` in both ScaledObject and TriggerAuthentication together")
}
switch val {
case stringEnable:
enableTLS = true
case stringDisable:
enableTLS = false
default:
return fmt.Errorf("error incorrect TLS value given, got %s", val)
}
}
if enableTLS {
certGiven := config.AuthParams["cert"] != ""
keyGiven := config.AuthParams["key"] != ""
if certGiven && !keyGiven {
return errors.New("key must be provided with cert")
}
if keyGiven && !certGiven {
return errors.New("cert must be provided with key")
}
connInfo.ca = config.AuthParams["ca"]
connInfo.cert = config.AuthParams["cert"]
connInfo.key = config.AuthParams["key"]
if value, found := config.AuthParams["keyPassword"]; found {
connInfo.keyPassword = value
} else {
connInfo.keyPassword = ""
}
}
connInfo.enableTLS = enableTLS
return nil
}

func parseRedisMetadata(config *ScalerConfig, parserFn redisAddressParser) (*redisMetadata, error) {
connInfo, err := parserFn(config.TriggerMetadata, config.ResolvedEnv, config.AuthParams)
if err != nil {
return nil, err
}
meta := redisMetadata{
connectionInfo: connInfo,
}

err = parseTLSConfigIntoConnectionInfo(config, &meta.connectionInfo)
if err != nil {
return nil, err
}

meta.listLength = defaultListLength
Expand Down Expand Up @@ -463,7 +510,11 @@ func getRedisClusterClient(ctx context.Context, info redisConnectionInfo) (*redi
Password: info.password,
}
if info.enableTLS {
options.TLSConfig = util.CreateTLSClientConfig(info.unsafeSsl)
tlsConfig, err := util.NewTLSConfigWithPassword(info.cert, info.key, info.keyPassword, info.ca, info.unsafeSsl)
if err != nil {
return nil, err
}
options.TLSConfig = tlsConfig
}

// confirm if connected
Expand All @@ -485,7 +536,11 @@ func getRedisSentinelClient(ctx context.Context, info redisConnectionInfo, dbInd
MasterName: info.sentinelMaster,
}
if info.enableTLS {
options.TLSConfig = util.CreateTLSClientConfig(info.unsafeSsl)
tlsConfig, err := util.NewTLSConfigWithPassword(info.cert, info.key, info.keyPassword, info.ca, info.unsafeSsl)
if err != nil {
return nil, err
}
options.TLSConfig = tlsConfig
}

// confirm if connected
Expand All @@ -504,7 +559,11 @@ func getRedisClient(ctx context.Context, info redisConnectionInfo, dbIndex int)
DB: dbIndex,
}
if info.enableTLS {
options.TLSConfig = util.CreateTLSClientConfig(info.unsafeSsl)
tlsConfig, err := util.NewTLSConfigWithPassword(info.cert, info.key, info.keyPassword, info.ca, info.unsafeSsl)
if err != nil {
return nil, err
}
options.TLSConfig = tlsConfig
}

// confirm if connected
Expand Down
65 changes: 49 additions & 16 deletions pkg/scalers/redis_scaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type parseRedisMetadataTestData struct {
metadata map[string]string
isError bool
authParams map[string]string
enableTLS bool
}

type redisMetricIdentifier struct {
Expand All @@ -33,48 +34,80 @@ type redisMetricIdentifier struct {

var testRedisMetadata = []parseRedisMetadataTestData{
// nothing passed
{map[string]string{}, true, map[string]string{}},
{map[string]string{}, true, map[string]string{}, false},
// properly formed listName
{map[string]string{"listName": "mylist", "listLength": "10", "addressFromEnv": "REDIS_HOST", "passwordFromEnv": "REDIS_PASSWORD"}, false, map[string]string{}},
{map[string]string{"listName": "mylist", "listLength": "10", "addressFromEnv": "REDIS_HOST", "passwordFromEnv": "REDIS_PASSWORD"}, false, map[string]string{}, false},
// properly formed hostPort
{map[string]string{"listName": "mylist", "listLength": "10", "hostFromEnv": "REDIS_HOST", "portFromEnv": "REDIS_PORT", "passwordFromEnv": "REDIS_PASSWORD"}, false, map[string]string{}},
{map[string]string{"listName": "mylist", "listLength": "10", "hostFromEnv": "REDIS_HOST", "portFromEnv": "REDIS_PORT", "passwordFromEnv": "REDIS_PASSWORD"}, false, map[string]string{}, false},
// properly formed hostPort
{map[string]string{"listName": "mylist", "listLength": "10", "addressFromEnv": "REDIS_HOST", "host": "REDIS_HOST", "port": "REDIS_PORT", "passwordFromEnv": "REDIS_PASSWORD"}, false, map[string]string{}},
{map[string]string{"listName": "mylist", "listLength": "10", "addressFromEnv": "REDIS_HOST", "host": "REDIS_HOST", "port": "REDIS_PORT", "passwordFromEnv": "REDIS_PASSWORD"}, false, map[string]string{}, false},
// improperly formed hostPort
{map[string]string{"listName": "mylist", "listLength": "10", "hostFromEnv": "REDIS_HOST", "passwordFromEnv": "REDIS_PASSWORD"}, true, map[string]string{}},
{map[string]string{"listName": "mylist", "listLength": "10", "hostFromEnv": "REDIS_HOST", "passwordFromEnv": "REDIS_PASSWORD"}, true, map[string]string{}, false},
// properly formed listName, empty address
{map[string]string{"listName": "mylist", "listLength": "10", "address": "", "password": ""}, true, map[string]string{}},
{map[string]string{"listName": "mylist", "listLength": "10", "address": "", "password": ""}, true, map[string]string{}, false},
// improperly formed listLength
{map[string]string{"listName": "mylist", "listLength": "AA", "addressFromEnv": "REDIS_HOST", "password": ""}, true, map[string]string{}},
{map[string]string{"listName": "mylist", "listLength": "AA", "addressFromEnv": "REDIS_HOST", "password": ""}, true, map[string]string{}, false},
// improperly formed activationListLength
{map[string]string{"listName": "mylist", "listLength": "1", "activationListLength": "AA", "addressFromEnv": "REDIS_HOST", "password": ""}, true, map[string]string{}},
{map[string]string{"listName": "mylist", "listLength": "1", "activationListLength": "AA", "addressFromEnv": "REDIS_HOST", "password": ""}, true, map[string]string{}, false},
// address does not resolve
{map[string]string{"listName": "mylist", "listLength": "0", "addressFromEnv": "REDIS_WRONG", "password": ""}, true, map[string]string{}},
{map[string]string{"listName": "mylist", "listLength": "0", "addressFromEnv": "REDIS_WRONG", "password": ""}, true, map[string]string{}, false},
// password is defined in the authParams
{map[string]string{"listName": "mylist", "listLength": "0", "addressFromEnv": "REDIS_WRONG"}, true, map[string]string{"password": ""}},
{map[string]string{"listName": "mylist", "listLength": "0", "addressFromEnv": "REDIS_WRONG"}, true, map[string]string{"password": ""}, false},
// address is defined in the authParams
{map[string]string{"listName": "mylist", "listLength": "0"}, false, map[string]string{"address": "localhost:6379"}},
{map[string]string{"listName": "mylist", "listLength": "0"}, false, map[string]string{"address": "localhost:6379"}, false},
// host and port is defined in the authParams
{map[string]string{"listName": "mylist", "listLength": "0"}, false, map[string]string{"host": "localhost", "port": "6379"}},
{map[string]string{"listName": "mylist", "listLength": "0"}, false, map[string]string{"host": "localhost", "port": "6379"}, false},
// enableTLS, TLS defined in the authParams only
{map[string]string{"listName": "mylist", "listLength": "0"}, false, map[string]string{"address": "localhost:6379", "tls": "enable", "ca": "caaa", "cert": "ceert", "key": "keey"}, true},
// enableTLS, TLS cert/key and assumed public CA
{map[string]string{"listName": "mylist", "listLength": "0"}, false, map[string]string{"address": "localhost:6379", "tls": "enable", "cert": "ceert", "key": "keey"}, true},
// enableTLS, TLS cert/key + key password and assumed public CA
{map[string]string{"listName": "mylist", "listLength": "0"}, false, map[string]string{"address": "localhost:6379", "tls": "enable", "cert": "ceert", "key": "keey", "keyPassword": "keeyPassword"}, true},
// enableTLS, TLS CA only
{map[string]string{"listName": "mylist", "listLength": "0"}, false, map[string]string{"address": "localhost:6379", "tls": "enable", "ca": "caaa"}, true},
// enableTLS is enabled by metadata
{map[string]string{"listName": "mylist", "listLength": "0", "enableTLS": "true"}, false, map[string]string{"address": "localhost:6379"}, true},
// enableTLS is defined both in authParams and metadata
{map[string]string{"listName": "mylist", "listLength": "0", "enableTLS": "true"}, true, map[string]string{"address": "localhost:6379", "tls": "disable"}, true},
// host only is defined in the authParams
{map[string]string{"listName": "mylist", "listLength": "0"}, true, map[string]string{"host": "localhost"}}}
{map[string]string{"listName": "mylist", "listLength": "0"}, true, map[string]string{"host": "localhost"}, false}}

var redisMetricIdentifiers = []redisMetricIdentifier{
{&testRedisMetadata[1], 0, "s0-redis-mylist"},
{&testRedisMetadata[1], 1, "s1-redis-mylist"},
}

func TestRedisParseMetadata(t *testing.T) {
testCaseNum := 1
testCaseNum := 0
for _, testData := range testRedisMetadata {
_, err := parseRedisMetadata(&ScalerConfig{TriggerMetadata: testData.metadata, ResolvedEnv: testRedisResolvedEnv, AuthParams: testData.authParams}, parseRedisAddress)
testCaseNum++
meta, err := parseRedisMetadata(&ScalerConfig{TriggerMetadata: testData.metadata, ResolvedEnv: testRedisResolvedEnv, AuthParams: testData.authParams}, parseRedisAddress)
if err != nil && !testData.isError {
t.Errorf("Expected success but got error for unit test # %v", testCaseNum)
}
if testData.isError && err == nil {
t.Errorf("Expected error but got success for unit test #%v", testCaseNum)
}
testCaseNum++
if testData.isError {
continue
}
if meta.connectionInfo.enableTLS != testData.enableTLS {
t.Errorf("Expected enableTLS to be set to %v but got %v for unit test #%v\n", testData.enableTLS, meta.connectionInfo.enableTLS, testCaseNum)
}
if meta.connectionInfo.enableTLS {
if meta.connectionInfo.ca != testData.authParams["ca"] {
t.Errorf("Expected ca to be set to %v but got %v for unit test #%v\n", testData.authParams["ca"], meta.connectionInfo.enableTLS, testCaseNum)
}
if meta.connectionInfo.cert != testData.authParams["cert"] {
t.Errorf("Expected cert to be set to %v but got %v for unit test #%v\n", testData.authParams["cert"], meta.connectionInfo.cert, testCaseNum)
}
if meta.connectionInfo.key != testData.authParams["key"] {
t.Errorf("Expected key to be set to %v but got %v for unit test #%v\n", testData.authParams["key"], meta.connectionInfo.key, testCaseNum)
}
if meta.connectionInfo.keyPassword != testData.authParams["keyPassword"] {
t.Errorf("Expected key to be set to %v but got %v for unit test #%v\n", testData.authParams["keyPassword"], meta.connectionInfo.key, testCaseNum)
}
}
}
}

Expand Down
19 changes: 3 additions & 16 deletions pkg/scalers/redis_streams_scaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,22 +265,9 @@ func parseRedisStreamsMetadata(config *ScalerConfig, parseFn redisAddressParser)
connectionInfo: connInfo,
}

meta.connectionInfo.enableTLS = defaultEnableTLS
if val, ok := config.TriggerMetadata["enableTLS"]; ok {
tls, err := strconv.ParseBool(val)
if err != nil {
return nil, fmt.Errorf("enableTLS parsing error %w", err)
}
meta.connectionInfo.enableTLS = tls
}

meta.connectionInfo.unsafeSsl = false
if val, ok := config.TriggerMetadata["unsafeSsl"]; ok {
parsedVal, err := strconv.ParseBool(val)
if err != nil {
return nil, fmt.Errorf("error parsing unsafeSsl: %w", err)
}
meta.connectionInfo.unsafeSsl = parsedVal
err = parseTLSConfigIntoConnectionInfo(config, &meta.connectionInfo)
if err != nil {
return nil, err
}

if val, ok := config.TriggerMetadata[streamNameMetadata]; ok {
Expand Down
37 changes: 37 additions & 0 deletions pkg/scalers/redis_streams_scaler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -737,6 +737,43 @@ func TestParseRedisClusterStreamsMetadata(t *testing.T) {
},
wantErr: nil,
},
{
name: "tls in auth param",
metadata: map[string]string{
"hosts": "a, b, c",
"ports": "1, 2, 3",
"stream": "my-stream",
"pendingEntriesCount": "5",
"consumerGroup": "consumer1",
},
authParams: map[string]string{
"password": "password",
"tls": "enable",
"ca": "caaa",
"cert": "ceert",
"key": "keey",
"keyPassword": "keeyPassword",
},
wantMeta: &redisStreamsMetadata{
streamName: "my-stream",
targetPendingEntriesCount: 5,
activationLagCount: 0,
consumerGroupName: "consumer1",
connectionInfo: redisConnectionInfo{
addresses: []string{"a:1", "b:2", "c:3"},
hosts: []string{"a", "b", "c"},
ports: []string{"1", "2", "3"},
password: "password",
enableTLS: true,
ca: "caaa",
cert: "ceert",
key: "keey",
keyPassword: "keeyPassword",
},
scaleFactor: xPendingFactor,
},
wantErr: nil,
},
{
name: "stream is provided",
metadata: map[string]string{
Expand Down