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); + } }