This is an automated email from the ASF dual-hosted git repository.

FrankChen021 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/druid.git


The following commit(s) were added to refs/heads/master by this push:
     new e44c2166880 feat: support redis TLS (#19666)
e44c2166880 is described below

commit e44c2166880417d425c835265cb04b85e75fcb08
Author: Hyeonho Kim <[email protected]>
AuthorDate: Wed Jul 15 21:22:21 2026 +0900

    feat: support redis TLS (#19666)
    
    * docs: druid.cache.enableTls
    
    * feat: TLS for redis
    
    * docs: update description
    
    * feat: skip tls hostname verification
    
    * fix: avoid using database from user input when cluster mode
    
    * test: coverage
---
 docs/development/extensions-contrib/redis-cache.md |  2 +
 .../druid/client/cache/RedisCacheConfig.java       | 23 ++++++
 .../druid/client/cache/RedisCacheFactory.java      | 57 ++++++++------
 .../druid/client/cache/RedisCacheConfigTest.java   | 91 ++++++++++++++++++++++
 4 files changed, 149 insertions(+), 24 deletions(-)

diff --git a/docs/development/extensions-contrib/redis-cache.md 
b/docs/development/extensions-contrib/redis-cache.md
index 63e0b9e509c..c1c76fe7488 100644
--- a/docs/development/extensions-contrib/redis-cache.md
+++ b/docs/development/extensions-contrib/redis-cache.md
@@ -84,6 +84,8 @@ Except for the properties above, there are some extra 
properties which can be cu
 | Properties |Description|Default|Required|
 |--------------------|-----------|-------|--------|
 |`druid.cache.password`| Password to access redis server/cluster | None |no|
+|`druid.cache.enableTls`| Whether to connect to the redis server/cluster over 
TLS | false |no|
+|`druid.cache.skipTlsHostnameVerification`| When TLS is enabled, skip 
verifying that the server certificate matches the redis hostname | false |no|
 |`druid.cache.expiration`|Expiration for cache entries | P1D |no|
 |`druid.cache.timeout`|Timeout for connecting to Redis and reading entries 
from Redis|PT2S|no|
 |`druid.cache.maxTotalConnections`|Max total connections to Redis|8|no|
diff --git 
a/extensions-contrib/redis-cache/src/main/java/org/apache/druid/client/cache/RedisCacheConfig.java
 
b/extensions-contrib/redis-cache/src/main/java/org/apache/druid/client/cache/RedisCacheConfig.java
index 4f36ea8383b..522b0677468 100644
--- 
a/extensions-contrib/redis-cache/src/main/java/org/apache/druid/client/cache/RedisCacheConfig.java
+++ 
b/extensions-contrib/redis-cache/src/main/java/org/apache/druid/client/cache/RedisCacheConfig.java
@@ -144,6 +144,19 @@ public class RedisCacheConfig
   @Min(0)
   private int database = Protocol.DEFAULT_DATABASE;
 
+  /**
+   * whether to connect to redis server/cluster over TLS.
+   */
+  @JsonProperty
+  private boolean enableTls = false;
+
+  /**
+   * whether to skip verifying that the server certificate matches the redis 
hostname when TLS is
+   * enabled. defaults to false (the hostname is verified)
+   */
+  @JsonProperty
+  private boolean skipTlsHostnameVerification = false;
+
   @JsonProperty
   private RedisClusterConfig cluster;
 
@@ -196,4 +209,14 @@ public class RedisCacheConfig
   {
     return database;
   }
+
+  public boolean getEnableTls()
+  {
+    return enableTls;
+  }
+
+  public boolean getSkipTlsHostnameVerification()
+  {
+    return skipTlsHostnameVerification;
+  }
 }
diff --git 
a/extensions-contrib/redis-cache/src/main/java/org/apache/druid/client/cache/RedisCacheFactory.java
 
b/extensions-contrib/redis-cache/src/main/java/org/apache/druid/client/cache/RedisCacheFactory.java
index 2ac5cbad9e0..2efe59b0292 100644
--- 
a/extensions-contrib/redis-cache/src/main/java/org/apache/druid/client/cache/RedisCacheFactory.java
+++ 
b/extensions-contrib/redis-cache/src/main/java/org/apache/druid/client/cache/RedisCacheFactory.java
@@ -19,13 +19,18 @@
 
 package org.apache.druid.client.cache;
 
+import com.google.common.annotations.VisibleForTesting;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.druid.java.util.common.IAE;
 import redis.clients.jedis.ConnectionPoolConfig;
+import redis.clients.jedis.DefaultJedisClientConfig;
 import redis.clients.jedis.HostAndPort;
+import redis.clients.jedis.JedisClientConfig;
 import redis.clients.jedis.JedisCluster;
 import redis.clients.jedis.JedisPool;
 import redis.clients.jedis.JedisPoolConfig;
+import redis.clients.jedis.SslOptions;
+import redis.clients.jedis.SslVerifyMode;
 
 import java.util.Arrays;
 import java.util.Set;
@@ -65,24 +70,12 @@ public class RedisCacheFactory
       poolConfig.setMaxIdle(config.getMaxIdleConnections());
       poolConfig.setMinIdle(config.getMinIdleConnections());
 
-      JedisCluster cluster;
-      if (config.getPassword() != null) {
-        cluster = new JedisCluster(
-            nodes,
-            config.getTimeout().getMillisecondsAsInt(), //connection timeout
-            config.getTimeout().getMillisecondsAsInt(), //read timeout
-            config.getCluster().getMaxRedirection(),
-            config.getPassword().getPassword(),
-            poolConfig
-        );
-      } else {
-        cluster = new JedisCluster(
-            nodes,
-            config.getTimeout().getMillisecondsAsInt(), //connection timeout 
and read timeout
-            config.getCluster().getMaxRedirection(),
-            poolConfig
-        );
-      }
+      JedisCluster cluster = new JedisCluster(
+          nodes,
+          buildClientConfig(config, 0),
+          config.getCluster().getMaxRedirection(),
+          poolConfig
+      );
 
       return new RedisClusterCache(cluster, config);
 
@@ -100,15 +93,31 @@ public class RedisCacheFactory
       return new RedisStandaloneCache(
           new JedisPool(
               poolConfig,
-              config.getHost(),
-              config.getPort(),
-              config.getTimeout().getMillisecondsAsInt(), //connection timeout 
and read timeout
-              config.getPassword() == null ? null : 
config.getPassword().getPassword(),
-              config.getDatabase(),
-              null
+              new HostAndPort(config.getHost(), config.getPort()),
+              buildClientConfig(config, config.getDatabase())
           ),
           config
       );
     }
   }
+
+  @VisibleForTesting
+  static JedisClientConfig buildClientConfig(RedisCacheConfig config, int 
database)
+  {
+    DefaultJedisClientConfig.Builder builder = DefaultJedisClientConfig
+        .builder()
+        .connectionTimeoutMillis(config.getTimeout().getMillisecondsAsInt())
+        .socketTimeoutMillis(config.getTimeout().getMillisecondsAsInt())
+        .password(config.getPassword() == null ? null : 
config.getPassword().getPassword())
+        .database(database)
+        .ssl(config.getEnableTls());
+
+    if (config.getEnableTls()) {
+      SslVerifyMode verifyMode =
+          config.getSkipTlsHostnameVerification() ? SslVerifyMode.CA : 
SslVerifyMode.FULL;
+      
builder.sslOptions(SslOptions.builder().sslVerifyMode(verifyMode).build());
+    }
+
+    return builder.build();
+  }
 }
diff --git 
a/extensions-contrib/redis-cache/src/test/java/org/apache/druid/client/cache/RedisCacheConfigTest.java
 
b/extensions-contrib/redis-cache/src/test/java/org/apache/druid/client/cache/RedisCacheConfigTest.java
index d1860636348..ecbe5cfd051 100644
--- 
a/extensions-contrib/redis-cache/src/test/java/org/apache/druid/client/cache/RedisCacheConfigTest.java
+++ 
b/extensions-contrib/redis-cache/src/test/java/org/apache/druid/client/cache/RedisCacheConfigTest.java
@@ -24,12 +24,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
 import com.github.fppt.jedismock.RedisServer;
 import com.github.fppt.jedismock.server.ServiceOptions;
 import org.apache.druid.java.util.common.IAE;
+import org.apache.druid.metadata.PasswordProvider;
 import org.hamcrest.Description;
 import org.hamcrest.Matcher;
 import org.junit.Assert;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import redis.clients.jedis.JedisClientConfig;
+import redis.clients.jedis.SslVerifyMode;
 
 import java.io.IOException;
 
@@ -232,4 +235,92 @@ public class RedisCacheConfigTest
     ));
     RedisCacheFactory.create(fromJson);
   }
+
+  @Test
+  public void testEnableTls() throws IOException
+  {
+    ObjectMapper mapper = new ObjectMapper();
+
+    RedisCacheConfig defaultConfig = mapper.readValue(
+        "{\"host\": \"localhost\", \"port\": 6379}",
+        RedisCacheConfig.class
+    );
+    Assert.assertFalse(defaultConfig.getEnableTls());
+
+    RedisCacheConfig tlsConfig = mapper.readValue(
+        "{\"host\": \"localhost\", \"port\": 6379, \"enableTls\": true}",
+        RedisCacheConfig.class
+    );
+    Assert.assertTrue(tlsConfig.getEnableTls());
+  }
+
+  @Test
+  public void testSkipTlsHostnameVerification() throws IOException
+  {
+    ObjectMapper mapper = new ObjectMapper();
+
+    RedisCacheConfig defaultConfig = mapper.readValue(
+        "{\"host\": \"localhost\", \"port\": 6379}",
+        RedisCacheConfig.class
+    );
+    Assert.assertFalse(defaultConfig.getSkipTlsHostnameVerification());
+
+    RedisCacheConfig skipConfig = mapper.readValue(
+        "{\"host\": \"localhost\", \"port\": 6379, 
\"skipTlsHostnameVerification\": true}",
+        RedisCacheConfig.class
+    );
+    Assert.assertTrue(skipConfig.getSkipTlsHostnameVerification());
+  }
+
+  @Test
+  public void testBuildClientConfig()
+  {
+    // TLS disabled: not SSL, no SSL options, database argument passed 
through, null password.
+    JedisClientConfig plain = RedisCacheFactory.buildClientConfig(new 
RedisCacheConfig(), 3);
+    Assert.assertFalse(plain.isSsl());
+    Assert.assertNull(plain.getSslOptions());
+    Assert.assertEquals(3, plain.getDatabase());
+    Assert.assertNull(plain.getPassword());
+
+    // TLS enabled with hostname verification (default) maps to 
SslVerifyMode.FULL, and a
+    // non-null password is forwarded.
+    RedisCacheConfig verifyConfig = new RedisCacheConfig()
+    {
+      @Override
+      public boolean getEnableTls()
+      {
+        return true;
+      }
+
+      @Override
+      public PasswordProvider getPassword()
+      {
+        return () -> "secret";
+      }
+    };
+    JedisClientConfig verify = 
RedisCacheFactory.buildClientConfig(verifyConfig, 0);
+    Assert.assertTrue(verify.isSsl());
+    Assert.assertEquals(SslVerifyMode.FULL, 
verify.getSslOptions().getSslVerifyMode());
+    Assert.assertEquals("secret", verify.getPassword());
+
+    // skipTlsHostnameVerification maps to SslVerifyMode.CA (chain verified, 
hostname skipped).
+    RedisCacheConfig skipConfig = new RedisCacheConfig()
+    {
+      @Override
+      public boolean getEnableTls()
+      {
+        return true;
+      }
+
+      @Override
+      public boolean getSkipTlsHostnameVerification()
+      {
+        return true;
+      }
+    };
+    Assert.assertEquals(
+        SslVerifyMode.CA,
+        RedisCacheFactory.buildClientConfig(skipConfig, 
0).getSslOptions().getSslVerifyMode()
+    );
+  }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to