From 3efbb8306e4230281c62c42e1383624d0ba95618 Mon Sep 17 00:00:00 2001 From: Miguel Prieto Date: Wed, 4 May 2022 15:59:38 -0400 Subject: [PATCH] Redis password authentication support (#2950) * Added support for password authentication in RedisStandaloneConfiguration, RedisClusterConfiguration and RedisSentinelConfiguration * - Added a Redis Setup documentation page to the docs - Added a comment in application.properties --- docs/docs/how-tos/redis.md | 38 +++++++++++++++++++ .../config/RedisClusterConfiguration.java | 32 +++++++++++++++- .../config/RedisSentinelConfiguration.java | 31 +++++++++++++-- .../config/RedisStandaloneConfiguration.java | 17 ++++++++- .../src/main/resources/application.properties | 3 +- 5 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 docs/docs/how-tos/redis.md diff --git a/docs/docs/how-tos/redis.md b/docs/docs/how-tos/redis.md new file mode 100644 index 0000000000..c0fb43997f --- /dev/null +++ b/docs/docs/how-tos/redis.md @@ -0,0 +1,38 @@ +# Redis Configuration + +By default conductor runs with an in-memory Redis mock. However, you +can change the configuration by setting the properties `conductor.db.type` and `conductor.redis.hosts`. + +## `conductor.db.type` + +| Value | Description | +|--------------------------------|----------------------------------------------------------------------------------------| +| dynomite | Dynomite Cluster. Dynomite is a proxy layer that provides sharding and replication. | +| memory | Uses an in-memory Redis mock. Should be used only for development and testing purposes.| +| redis_cluster | Redis Cluster configuration. | +| redis_sentinel | Redis Sentinel configuration. | +| redis_standalone | Redis Standalone configuration. | + + + +## `conductor.redis.hosts` + +Expected format is `host:port:rack` separated by semicolon, e.g.: + +```properties +conductor.redis.hosts=host0:6379:us-east-1c;host1:6379:us-east-1c;host2:6379:us-east-1c +``` + +### Auth Support + +Password authentication is supported. The password should be set as the 4th param of the first host `host:port:rack:password`, e.g.: + +```properties +conductor.redis.hosts=host0:6379:us-east-1c:my_str0ng_pazz;host1:6379:us-east-1c;host2:6379:us-east-1c +``` + + +**Notes** + +- In a cluster, all nodes use the same password. +- In a sentinel configuration, sentinels and redis nodes use the same password. diff --git a/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisClusterConfiguration.java b/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisClusterConfiguration.java index 0b4e4389e6..b98e038f74 100644 --- a/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisClusterConfiguration.java +++ b/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisClusterConfiguration.java @@ -12,25 +12,35 @@ */ package com.netflix.conductor.redis.config; +import java.util.List; import java.util.Set; import java.util.stream.Collectors; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import com.netflix.conductor.core.config.ConductorProperties; import com.netflix.conductor.redis.jedis.JedisCluster; +import com.netflix.dyno.connectionpool.Host; import com.netflix.dyno.connectionpool.HostSupplier; import com.netflix.dyno.connectionpool.TokenMapSupplier; import redis.clients.jedis.HostAndPort; +import redis.clients.jedis.Protocol; import redis.clients.jedis.commands.JedisCommands; @Configuration(proxyBeanMethods = false) @ConditionalOnProperty(name = "conductor.db.type", havingValue = "redis_cluster") public class RedisClusterConfiguration extends JedisCommandsConfigurer { + private static final Logger log = LoggerFactory.getLogger(JedisCommandsConfigurer.class); + + // Same as redis.clients.jedis.BinaryJedisCluster + protected static final int DEFAULT_MAX_ATTEMPTS = 5; + @Override protected JedisCommands createJedisCommands( RedisProperties properties, @@ -43,7 +53,25 @@ protected JedisCommands createJedisCommands( hostSupplier.getHosts().stream() .map(h -> new HostAndPort(h.getHostName(), h.getPort())) .collect(Collectors.toSet()); - return new JedisCluster( - new redis.clients.jedis.JedisCluster(hosts, genericObjectPoolConfig)); + String password = getPassword(hostSupplier.getHosts()); + + if (password != null) { + log.info("Connecting to Redis Cluster with AUTH"); + return new JedisCluster( + new redis.clients.jedis.JedisCluster( + hosts, + Protocol.DEFAULT_TIMEOUT, + Protocol.DEFAULT_TIMEOUT, + DEFAULT_MAX_ATTEMPTS, + password, + genericObjectPoolConfig)); + } else { + return new JedisCluster( + new redis.clients.jedis.JedisCluster(hosts, genericObjectPoolConfig)); + } + } + + private String getPassword(List hosts) { + return hosts.isEmpty() ? null : hosts.get(0).getPassword(); } } diff --git a/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisSentinelConfiguration.java b/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisSentinelConfiguration.java index 90a3fd6cd3..89fa5b8cc9 100644 --- a/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisSentinelConfiguration.java +++ b/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisSentinelConfiguration.java @@ -13,6 +13,7 @@ package com.netflix.conductor.redis.config; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.apache.commons.pool2.impl.GenericObjectPoolConfig; @@ -28,6 +29,7 @@ import com.netflix.dyno.connectionpool.TokenMapSupplier; import redis.clients.jedis.JedisSentinelPool; +import redis.clients.jedis.Protocol; import redis.clients.jedis.commands.JedisCommands; @Configuration(proxyBeanMethods = false) @@ -59,8 +61,31 @@ protected JedisCommands createJedisCommands( for (Host host : hostSupplier.getHosts()) { sentinels.add(host.getHostName() + ":" + host.getPort()); } - return new JedisSentinel( - new JedisSentinelPool( - properties.getClusterName(), sentinels, genericObjectPoolConfig)); + // We use the password of the first sentinel host as password and sentinelPassword + String password = getPassword(hostSupplier.getHosts()); + if (password != null) { + return new JedisSentinel( + new JedisSentinelPool( + properties.getClusterName(), + sentinels, + genericObjectPoolConfig, + Protocol.DEFAULT_TIMEOUT, + Protocol.DEFAULT_TIMEOUT, + password, + Protocol.DEFAULT_DATABASE, + null, + Protocol.DEFAULT_TIMEOUT, + Protocol.DEFAULT_TIMEOUT, + password, + null)); + } else { + return new JedisSentinel( + new JedisSentinelPool( + properties.getClusterName(), sentinels, genericObjectPoolConfig)); + } + } + + private String getPassword(List hosts) { + return hosts.isEmpty() ? null : hosts.get(0).getPassword(); } } diff --git a/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisStandaloneConfiguration.java b/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisStandaloneConfiguration.java index e6f8c87694..8882e54036 100644 --- a/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisStandaloneConfiguration.java +++ b/redis-persistence/src/main/java/com/netflix/conductor/redis/config/RedisStandaloneConfiguration.java @@ -25,6 +25,7 @@ import redis.clients.jedis.JedisPool; import redis.clients.jedis.JedisPoolConfig; +import redis.clients.jedis.Protocol; import redis.clients.jedis.commands.JedisCommands; @Configuration(proxyBeanMethods = false) @@ -44,6 +45,20 @@ protected JedisCommands createJedisCommands( config.setMaxTotal(properties.getMaxConnectionsPerHost()); log.info("Starting conductor server using redis_standalone."); Host host = hostSupplier.getHosts().get(0); - return new JedisStandalone(new JedisPool(config, host.getHostName(), host.getPort())); + return new JedisStandalone(getJedisPool(config, host)); + } + + private JedisPool getJedisPool(JedisPoolConfig config, Host host) { + if (host.getPassword() != null) { + log.info("Connecting to Redis Standalone with AUTH"); + return new JedisPool( + config, + host.getHostName(), + host.getPort(), + Protocol.DEFAULT_TIMEOUT, + host.getPassword()); + } else { + return new JedisPool(config, host.getHostName(), host.getPort()); + } } } diff --git a/server/src/main/resources/application.properties b/server/src/main/resources/application.properties index 69b0ebf98f..4ffb2cd269 100644 --- a/server/src/main/resources/application.properties +++ b/server/src/main/resources/application.properties @@ -18,8 +18,9 @@ conductor.db.type=memory conductor.indexing.enabled=false -#Dynomite Cluster details. +#Redis configuration details. #format is host:port:rack separated by semicolon +#Auth is supported. Password is taken from host[0]. format: host:port:rack:password conductor.redis.hosts=host1:port:rack;host2:port:rack:host3:port:rack #namespace for the keys stored in Dynomite/Redis