Skip to content

Commit

Permalink
Add tls authentication for redis scaler (kedacore#4979)
Browse files Browse the repository at this point in the history
Signed-off-by: zhangchao <[email protected]>
Signed-off-by: anton.lysina <[email protected]>
  • Loading branch information
Taction authored and toniiiik committed Jan 15, 2024
1 parent 1d329f7 commit b3dc3bf
Show file tree
Hide file tree
Showing 5 changed files with 167 additions and 50 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ Here is an overview of all new **experimental** features:
- **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
- **General**: Add validations for stabilizationWindowSeconds ([#4976](https://github.com/kedacore/keda/issues/4976))
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

0 comments on commit b3dc3bf

Please sign in to comment.