From 74f7d184cfaba07dd9c825d5ebd328d04a7114ea Mon Sep 17 00:00:00 2001
From: langshiquan <576506402@qq.com>
Date: Sun, 23 Apr 2023 10:30:39 +0800
Subject: [PATCH] Add SSL support for sentinel-datasource-redis (#3045)
---
.../sentinel-datasource-redis/pom.xml | 2 +-
.../datasource/redis/RedisDataSource.java | 72 +++++-
.../redis/config/RedisConnectionConfig.java | 236 +++++++++++++++++-
.../redis/RedisConnectionConfigTest.java | 29 +++
4 files changed, 322 insertions(+), 17 deletions(-)
diff --git a/sentinel-extension/sentinel-datasource-redis/pom.xml b/sentinel-extension/sentinel-datasource-redis/pom.xml
index 2c5f05be..d60da986 100644
--- a/sentinel-extension/sentinel-datasource-redis/pom.xml
+++ b/sentinel-extension/sentinel-datasource-redis/pom.xml
@@ -15,7 +15,7 @@
1.8
1.8
- 5.0.1.RELEASE
+ 5.3.1.RELEASE
0.1.6
diff --git a/sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/RedisDataSource.java b/sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/RedisDataSource.java
index 5888fc69..d10bcf08 100644
--- a/sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/RedisDataSource.java
+++ b/sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/RedisDataSource.java
@@ -25,7 +25,9 @@ import com.alibaba.csp.sentinel.util.StringUtil;
import io.lettuce.core.RedisClient;
import io.lettuce.core.RedisURI;
+import io.lettuce.core.SslOptions;
import io.lettuce.core.api.sync.RedisCommands;
+import io.lettuce.core.cluster.ClusterClientOptions;
import io.lettuce.core.cluster.RedisClusterClient;
import io.lettuce.core.cluster.api.sync.RedisAdvancedClusterCommands;
import io.lettuce.core.cluster.pubsub.StatefulRedisClusterPubSubConnection;
@@ -33,10 +35,10 @@ import io.lettuce.core.pubsub.RedisPubSubAdapter;
import io.lettuce.core.pubsub.StatefulRedisPubSubConnection;
import io.lettuce.core.pubsub.api.sync.RedisPubSubCommands;
+import java.io.File;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.TimeUnit;
/**
*
@@ -94,19 +96,70 @@ public class RedisDataSource extends AbstractDataSource {
subscribeFromChannel(channel);
}
+ /**
+ * init SslOptions, support jks or pem format
+ *
+ * @param connectionConfig Redis connection config
+ * @return a new SslOptions
+ */
+ private SslOptions initSslOptions(RedisConnectionConfig connectionConfig) {
+ if (!connectionConfig.isSslEnable()){
+ return null;
+ }
+
+ SslOptions.Builder sslOptionsBuilder = SslOptions.builder();
+
+ if (connectionConfig.getTrustedCertificatesPath() != null){
+ if (connectionConfig.getTrustedCertificatesPath().endsWith(".jks")){
+ // if the value is end with .jks,think it is java key store format,to invoke truststore method
+ sslOptionsBuilder.truststore(
+ new File(connectionConfig.getTrustedCertificatesPath()),
+ connectionConfig.getTrustedCertificatesJksPassword()
+ );
+ } else {
+ // if the value is not end with .jks,think it is pem format,to invoke trustManager method
+ sslOptionsBuilder.trustManager(new File(connectionConfig.getTrustedCertificatesPath()));
+ }
+ }
+
+ if (connectionConfig.getKeyCertChainFilePath() != null || connectionConfig.getKeyFilePath() != null) {
+ if (connectionConfig.getKeyFilePath().endsWith(".jks")){
+ sslOptionsBuilder.keystore(
+ new File(connectionConfig.getKeyCertChainFilePath()),
+ connectionConfig.getKeyFilePassword() == null ? null : connectionConfig.getKeyFilePassword().toCharArray()
+ );
+ } else {
+ sslOptionsBuilder.keyManager(
+ new File(connectionConfig.getKeyCertChainFilePath()),
+ new File(connectionConfig.getKeyFilePath()),
+ connectionConfig.getKeyFilePassword() == null ? null : connectionConfig.getKeyFilePassword().toCharArray()
+ );
+ }
+ }
+ return sslOptionsBuilder.build();
+ }
+
/**
* Build Redis client fromm {@code RedisConnectionConfig}.
*
* @return a new {@link RedisClient}
*/
private RedisClient getRedisClient(RedisConnectionConfig connectionConfig) {
+ RedisClient redisClient;
if (connectionConfig.getRedisSentinels().size() == 0) {
RecordLog.info("[RedisDataSource] Creating stand-alone mode Redis client");
- return getRedisStandaloneClient(connectionConfig);
+ redisClient = getRedisStandaloneClient(connectionConfig);
} else {
RecordLog.info("[RedisDataSource] Creating Redis Sentinel mode Redis client");
- return getRedisSentinelClient(connectionConfig);
+ redisClient = getRedisSentinelClient(connectionConfig);
}
+ SslOptions sslOptions = initSslOptions(connectionConfig);
+ if (sslOptions != null){
+ redisClient.setOptions(
+ ClusterClientOptions.builder().sslOptions(sslOptions).build()
+ );
+ }
+ return redisClient;
}
private RedisClusterClient getRedisClusterClient(RedisConnectionConfig connectionConfig) {
@@ -119,6 +172,7 @@ public class RedisDataSource extends AbstractDataSource {
RedisURI.Builder clusterRedisUriBuilder = RedisURI.builder();
clusterRedisUriBuilder.withHost(config.getHost())
.withPort(config.getPort())
+ .withSsl(config.isSslEnable())
.withTimeout(Duration.ofMillis(connectionConfig.getTimeout()));
//All redis nodes must have same password
if (password != null) {
@@ -126,9 +180,17 @@ public class RedisDataSource extends AbstractDataSource {
}
redisUris.add(clusterRedisUriBuilder.build());
}
- return RedisClusterClient.create(redisUris);
+ RedisClusterClient redisClusterClient = RedisClusterClient.create(redisUris);
+ SslOptions sslOptions = initSslOptions(connectionConfig);
+ if (sslOptions != null){
+ redisClusterClient.setOptions(
+ ClusterClientOptions.builder().sslOptions(sslOptions).build()
+ );
+ }
+ return redisClusterClient;
}
+
private RedisClient getRedisStandaloneClient(RedisConnectionConfig connectionConfig) {
char[] password = connectionConfig.getPassword();
String clientName = connectionConfig.getClientName();
@@ -136,6 +198,7 @@ public class RedisDataSource extends AbstractDataSource {
redisUriBuilder.withHost(connectionConfig.getHost())
.withPort(connectionConfig.getPort())
.withDatabase(connectionConfig.getDatabase())
+ .withSsl(connectionConfig.isSslEnable())
.withTimeout(Duration.ofMillis(connectionConfig.getTimeout()));
if (password != null) {
redisUriBuilder.withPassword(connectionConfig.getPassword());
@@ -160,6 +223,7 @@ public class RedisDataSource extends AbstractDataSource {
sentinelRedisUriBuilder.withClientName(clientName);
}
sentinelRedisUriBuilder.withSentinelMasterId(connectionConfig.getRedisSentinelMasterId())
+ .withSsl(connectionConfig.isSslEnable())
.withTimeout(Duration.ofMillis(connectionConfig.getTimeout()));
return RedisClient.create(sentinelRedisUriBuilder.build());
}
diff --git a/sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/config/RedisConnectionConfig.java b/sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/config/RedisConnectionConfig.java
index 98aa3cd7..62e9b549 100644
--- a/sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/config/RedisConnectionConfig.java
+++ b/sentinel-extension/sentinel-datasource-redis/src/main/java/com/alibaba/csp/sentinel/datasource/redis/config/RedisConnectionConfig.java
@@ -51,6 +51,12 @@ public class RedisConnectionConfig {
private String host;
private String redisSentinelMasterId;
private int port;
+ private boolean sslEnable;
+ private String trustedCertificatesPath;
+ private String trustedCertificatesJksPassword;
+ private String keyCertChainFilePath;
+ private String keyFilePath;
+ private String keyFilePassword;
private int database;
private String clientName;
private char[] password;
@@ -329,6 +335,12 @@ public class RedisConnectionConfig {
private int database;
private String clientName;
private char[] password;
+ private boolean sslEnable;
+ private String trustedCertificatesPath;
+ private String trustedCertificatesJksPassword;
+ private String keyCertChainFilePath;
+ private String keyFilePath;
+ private String keyFilePassword;
private long timeout = DEFAULT_TIMEOUT_MILLISECONDS;
private final List redisSentinels = new ArrayList();
private final List redisClusters = new ArrayList();
@@ -563,6 +575,7 @@ public class RedisConnectionConfig {
return this;
}
+
/**
* Configures authentication.
*
@@ -619,6 +632,75 @@ public class RedisConnectionConfig {
return this;
}
+ /**
+ * Sets the sslEnable.
+ *
+ * @param sslEnable sslEnable
+ * @return the value of Builder
+ */
+ public RedisConnectionConfig.Builder withSslEnable(boolean sslEnable) {
+ this.sslEnable = sslEnable;
+ return this;
+ }
+
+ /**
+ * Sets the trustedCertificatesPath.
+ *
+ * @param trustedCertificatesPath trustedCertificatesPath
+ * @return the value of Builder
+ */
+ public RedisConnectionConfig.Builder withTrustedCertificatesPath(String trustedCertificatesPath) {
+
+ AssertUtil.notEmpty(trustedCertificatesPath, "trusted certificates path must not empty");
+
+ this.trustedCertificatesPath = trustedCertificatesPath;
+ return this;
+ }
+
+ /**
+ * Sets the trustedCertificatesJksPassword.
+ *
+ * @param trustedCertificatesJksPassword trustedCertificatesJksPassword
+ * @return the value of Builder
+ */
+ public RedisConnectionConfig.Builder withTrustedCertificatesJksPassword(String trustedCertificatesJksPassword) {
+ this.trustedCertificatesJksPassword = trustedCertificatesJksPassword;
+ return this;
+ }
+
+ /**
+ * Sets the keyCertChainFilePath.
+ *
+ * @param keyCertChainFilePath keyCertChainFilePath
+ * @return the value of Builder
+ */
+ public RedisConnectionConfig.Builder withKeyCertChainFilePath(String keyCertChainFilePath) {
+ this.keyCertChainFilePath = keyCertChainFilePath;
+ return this;
+ }
+
+ /**
+ * Sets the keyFilePath.
+ *
+ * @param keyFilePath keyFilePath
+ * @return the value of Builder
+ */
+ public RedisConnectionConfig.Builder withKeyFilePath(String keyFilePath) {
+ this.keyFilePath = keyFilePath;
+ return this;
+ }
+
+ /**
+ * Sets the keyFilePassword.
+ *
+ * @param keyFilePassword keyFilePassword
+ * @return the value of Builder
+ */
+ public RedisConnectionConfig.Builder withKeyFilePassword(String keyFilePassword) {
+ this.keyFilePassword = keyFilePassword;
+ return this;
+ }
+
/**
* @return the RedisConnectionConfig.
*/
@@ -630,32 +712,41 @@ public class RedisConnectionConfig {
+ "Sentinel");
}
- RedisConnectionConfig redisURI = new RedisConnectionConfig();
- redisURI.setHost(host);
- redisURI.setPort(port);
+ RedisConnectionConfig redisConnectionConfig = new RedisConnectionConfig();
+ redisConnectionConfig.setHost(host);
+ redisConnectionConfig.setPort(port);
- if (password != null) {
- redisURI.setPassword(password);
+ if (sslEnable){
+ redisConnectionConfig.setSslEnable(true);
+ redisConnectionConfig.setTrustedCertificatesPath(trustedCertificatesPath);
+ redisConnectionConfig.setTrustedCertificatesJksPassword(trustedCertificatesJksPassword);
+ redisConnectionConfig.setKeyCertChainFilePath(keyCertChainFilePath);
+ redisConnectionConfig.setKeyFilePath(keyFilePath);
+ redisConnectionConfig.setKeyFilePassword(keyFilePassword);
}
- redisURI.setDatabase(database);
- redisURI.setClientName(clientName);
+ if (password != null) {
+ redisConnectionConfig.setPassword(password);
+ }
- redisURI.setRedisSentinelMasterId(redisSentinelMasterId);
+ redisConnectionConfig.setDatabase(database);
+ redisConnectionConfig.setClientName(clientName);
+
+ redisConnectionConfig.setRedisSentinelMasterId(redisSentinelMasterId);
for (RedisHostAndPort sentinel : redisSentinels) {
- redisURI.getRedisSentinels().add(
+ redisConnectionConfig.getRedisSentinels().add(
new RedisConnectionConfig(sentinel.getHost(), sentinel.getPort(), timeout));
}
for (RedisHostAndPort sentinel : redisClusters) {
- redisURI.getRedisClusters().add(
+ redisConnectionConfig.getRedisClusters().add(
new RedisConnectionConfig(sentinel.getHost(), sentinel.getPort(), timeout));
}
- redisURI.setTimeout(timeout);
+ redisConnectionConfig.setTimeout(timeout);
- return redisURI;
+ return redisConnectionConfig;
}
}
@@ -665,4 +756,125 @@ public class RedisConnectionConfig {
private static boolean isValidPort(int port) {
return port >= 0 && port <= 65535;
}
+
+ /**
+ * Gets the value of trustedCertificatesPath.
+ *
+ * @return the value of trustedCertificatesPath
+ */
+ public String getTrustedCertificatesPath() {
+ return trustedCertificatesPath;
+ }
+
+ /**
+ * Sets the trustedCertificatesPath.
+ *
+ *
You can use getTrustedCertificatesPath() to get the value of trustedCertificatesPath
+ *
+ * @param trustedCertificatesPath trustedCertificatesPath
+ */
+ public void setTrustedCertificatesPath(String trustedCertificatesPath) {
+ this.trustedCertificatesPath = trustedCertificatesPath;
+ }
+
+ /**
+ * Gets the value of trustedCertificatesJksPassword.
+ *
+ * @return the value of trustedCertificatesJksPassword
+ */
+ public String getTrustedCertificatesJksPassword() {
+ return trustedCertificatesJksPassword;
+ }
+
+ /**
+ * Sets the trustedCertificatesJksPassword.
+ *
+ *
You can use getTrustedCertificatesJksPassword() to get the value of trustedCertificatesJksPassword
+ *
+ * @param trustedCertificatesJksPassword trustedCertificatesJksPassword
+ */
+ public void setTrustedCertificatesJksPassword(String trustedCertificatesJksPassword) {
+ this.trustedCertificatesJksPassword = trustedCertificatesJksPassword;
+ }
+
+ /**
+ * Gets the value of keyCertChainFilePath.
+ *
+ * @return the value of keyCertChainFilePath
+ */
+ public String getKeyCertChainFilePath() {
+ return keyCertChainFilePath;
+ }
+
+ /**
+ * Sets the keyCertChainFilePath.
+ *
+ *
You can use getKeyCertChainFilePath() to get the value of keyCertChainFilePath
+ *
+ * @param keyCertChainFilePath keyCertChainFilePath
+ */
+ public void setKeyCertChainFilePath(String keyCertChainFilePath) {
+ this.keyCertChainFilePath = keyCertChainFilePath;
+ }
+
+ /**
+ * Gets the value of keyFilePath.
+ *
+ * @return the value of keyFilePath
+ */
+ public String getKeyFilePath() {
+ return keyFilePath;
+ }
+
+ /**
+ * Sets the keyFilePath.
+ *
+ *
You can use getKeyFilePath() to get the value of keyFilePath
+ *
+ * @param keyFilePath keyFilePath
+ */
+ public void setKeyFilePath(String keyFilePath) {
+ this.keyFilePath = keyFilePath;
+ }
+
+ /**
+ * Gets the value of keyFilePassword.
+ *
+ * @return the value of keyFilePassword
+ */
+ public String getKeyFilePassword() {
+ return keyFilePassword;
+ }
+
+ /**
+ * Sets the keyFilePassword.
+ *
+ *
You can use getKeyFilePassword() to get the value of keyFilePassword
+ *
+ * @param keyFilePassword keyFilePassword
+ */
+ public void setKeyFilePassword(String keyFilePassword) {
+ this.keyFilePassword = keyFilePassword;
+ }
+
+ /**
+ * Sets the sslEnable.
+ *
+ *
You can use isSslEnable() to get the value of sslEnable
+ *
+ * @param sslEnable sslEnable
+ */
+ public void setSslEnable(boolean sslEnable) {
+ this.sslEnable = sslEnable;
+ }
+
+
+ /**
+ * Gets the value of sslEnable.
+ *
+ * @return the value of sslEnable
+ */
+ public boolean isSslEnable() {
+ return sslEnable;
+ }
}
diff --git a/sentinel-extension/sentinel-datasource-redis/src/test/java/com/alibaba/csp/sentinel/datasource/redis/RedisConnectionConfigTest.java b/sentinel-extension/sentinel-datasource-redis/src/test/java/com/alibaba/csp/sentinel/datasource/redis/RedisConnectionConfigTest.java
index b9ffb9b4..b9ea8ee4 100644
--- a/sentinel-extension/sentinel-datasource-redis/src/test/java/com/alibaba/csp/sentinel/datasource/redis/RedisConnectionConfigTest.java
+++ b/sentinel-extension/sentinel-datasource-redis/src/test/java/com/alibaba/csp/sentinel/datasource/redis/RedisConnectionConfigTest.java
@@ -35,6 +35,7 @@ public class RedisConnectionConfigTest {
Assert.assertEquals(host, redisConnectionConfig.getHost());
Assert.assertEquals(RedisConnectionConfig.DEFAULT_REDIS_PORT, redisConnectionConfig.getPort());
Assert.assertEquals(RedisConnectionConfig.DEFAULT_TIMEOUT_MILLISECONDS, redisConnectionConfig.getTimeout());
+ Assert.assertFalse(redisConnectionConfig.isSslEnable());
}
@Test
@@ -133,4 +134,32 @@ public class RedisConnectionConfigTest {
Assert.assertNull(redisConnectionConfig.getHost());
Assert.assertEquals(3, redisConnectionConfig.getRedisClusters().size());
}
+
+ @Test
+ public void testRedisSsl() throws Exception {
+ String host = "localhost";
+ int port = 1879;
+ String trustedCertificatesPath = "trustedCertificatesPath";
+ String trustedCertificatesJksPassword = "trustedCertificatesJksPassword";
+ String keyCertChainFilePath = "keyCertChainFilePath";
+ String keyFilePath = "keyFilePath";
+ String keyFilePassword = "keyFilePassword";
+ RedisConnectionConfig redisConnectionConfig = RedisConnectionConfig.Builder.redis(host)
+ .withHost(host)
+ .withPort(port)
+ .withSslEnable(true)
+ .withTrustedCertificatesPath(trustedCertificatesPath)
+ .withTrustedCertificatesJksPassword(trustedCertificatesJksPassword)
+ .withKeyCertChainFilePath(keyCertChainFilePath)
+ .withKeyFilePath(keyFilePath)
+ .withKeyFilePassword(keyFilePassword)
+ .build();
+
+ Assert.assertTrue(redisConnectionConfig.isSslEnable());
+ Assert.assertEquals(redisConnectionConfig.getTrustedCertificatesPath(), trustedCertificatesPath);
+ Assert.assertEquals(redisConnectionConfig.getTrustedCertificatesJksPassword(), trustedCertificatesJksPassword);
+ Assert.assertEquals(redisConnectionConfig.getKeyCertChainFilePath(), keyCertChainFilePath);
+ Assert.assertEquals(redisConnectionConfig.getKeyFilePath(), keyFilePath);
+ Assert.assertEquals(redisConnectionConfig.getKeyFilePassword(), keyFilePassword);
+ }
}