diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 3e1aa5a8e6..432caa4c27 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -548,6 +548,14 @@ redis: db: "${REDIS_DB:0}" # db password password: "${REDIS_PASSWORD:}" + # ssl config + ssl: + enabled: "${TB_REDIS_SSL_ENABLED:true}" + truststoreLocation: "${TB_REDIS_SSL_TRUSTSTORE_LOCATION:}" + truststorePassword: "${TB_REDIS_SSL_TRUSTSTORE_PASSWORD:}" + # client authentication could be optional and depends on redis server configuration + keystoreLocation: "${TB_REDIS_SSL_KEYSTORE_LOCATION:}" + keystorePassword: "${TB_REDIS_SSL_KEYSTORE_PASSWORD:}" # pool config pool_config: maxTotal: "${REDIS_POOL_CONFIG_MAX_TOTAL:128}" diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java index 36f3dddc3d..dc0b301ffc 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisCacheConfiguration.java @@ -16,6 +16,8 @@ package org.thingsboard.server.cache; import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; @@ -35,6 +37,12 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; import redis.clients.jedis.JedisPoolConfig; +import javax.net.ssl.KeyManagerFactory; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import javax.net.ssl.TrustManagerFactory; +import java.io.FileInputStream; +import java.security.KeyStore; import java.time.Duration; import java.util.ArrayList; import java.util.Collections; @@ -44,6 +52,7 @@ import java.util.List; @ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") @EnableCaching @Data +@Slf4j public abstract class TBRedisCacheConfiguration { private static final String COMMA = ","; @@ -90,6 +99,9 @@ public abstract class TBRedisCacheConfiguration { return loadFactory(); } + @Autowired + private TbRedisSslCredentialsConfiguration redisSslCredentials; + protected abstract JedisConnectionFactory loadFactory(); /** @@ -149,4 +161,33 @@ public abstract class TBRedisCacheConfiguration { } return result; } + + protected SSLSocketFactory createSslSocketFactory() { + try { + KeyStore trustStore = KeyStore.getInstance("jks"); + trustStore.load(new FileInputStream(redisSslCredentials.getTruststoreLocation()), redisSslCredentials.getTruststorePassword().toCharArray()); + + TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance("X509"); + trustManagerFactory.init(trustStore); + + SSLContext sslContext = SSLContext.getInstance("TLS"); + + // client authentication is optional + if (redisSslCredentials.getKeystoreLocation() != null && redisSslCredentials.getKeystorePassword() != null) { + KeyStore keyStore = KeyStore.getInstance("pkcs12"); + keyStore.load(new FileInputStream(redisSslCredentials.getKeystoreLocation()), redisSslCredentials.getKeystorePassword().toCharArray()); + + KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("PKIX"); + keyManagerFactory.init(keyStore, redisSslCredentials.getKeystorePassword().toCharArray()); + + sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null); + } else { + sslContext.init(null, trustManagerFactory.getTrustManagers(), null); + } + return sslContext.getSocketFactory(); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java index 0a378103b0..63517a98ac 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisClusterConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisClusterConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @Configuration @@ -39,15 +40,29 @@ public class TBRedisClusterConfiguration extends TBRedisCacheConfiguration { @Value("${redis.password:}") private String password; + @Value("${redis.ssl.enabled:}") + private boolean useSsl; + public JedisConnectionFactory loadFactory() { RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(); clusterConfiguration.setClusterNodes(getNodes(clusterNodes)); clusterConfiguration.setMaxRedirects(maxRedirects); clusterConfiguration.setPassword(password); - if (useDefaultPoolConfig) { - return new JedisConnectionFactory(clusterConfiguration); - } else { - return new JedisConnectionFactory(clusterConfiguration, buildPoolConfig()); + return new JedisConnectionFactory(clusterConfiguration, buildClientConfig()); + } + + private JedisClientConfiguration buildClientConfig() { + JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder(); + if (!useDefaultPoolConfig) { + jedisClientConfigurationBuilder + .usePooling() + .poolConfig(buildPoolConfig()); } + if (useSsl) { + jedisClientConfigurationBuilder + .useSsl() + .sslSocketFactory(createSslSocketFactory()); + } + return jedisClientConfigurationBuilder.build(); } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java index 78cb445d82..dbfb3bce73 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisSentinelConfiguration.java @@ -20,6 +20,7 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.Configuration; import org.springframework.data.redis.connection.RedisSentinelConfiguration; +import org.springframework.data.redis.connection.jedis.JedisClientConfiguration; import org.springframework.data.redis.connection.jedis.JedisConnectionFactory; @Configuration @@ -42,6 +43,9 @@ public class TBRedisSentinelConfiguration extends TBRedisCacheConfiguration { @Value("${redis.db:}") private Integer database; + @Value("${redis.ssl.enabled:}") + private boolean useSsl; + @Value("${redis.password:}") private String password; @@ -52,11 +56,21 @@ public class TBRedisSentinelConfiguration extends TBRedisCacheConfiguration { redisSentinelConfiguration.setSentinelPassword(sentinelPassword); redisSentinelConfiguration.setPassword(password); redisSentinelConfiguration.setDatabase(database); - if (useDefaultPoolConfig) { - return new JedisConnectionFactory(redisSentinelConfiguration); - } else { - return new JedisConnectionFactory(redisSentinelConfiguration, buildPoolConfig()); - } + return new JedisConnectionFactory(redisSentinelConfiguration, buildClientConfig()); } + private JedisClientConfiguration buildClientConfig() { + JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder(); + if (!useDefaultPoolConfig) { + jedisClientConfigurationBuilder + .usePooling() + .poolConfig(buildPoolConfig()); + } + if (useSsl) { + jedisClientConfigurationBuilder + .useSsl() + .sslSocketFactory(createSslSocketFactory()); + } + return jedisClientConfigurationBuilder.build(); + } } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java index aae6ecd2a3..55065e443f 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TBRedisStandaloneConfiguration.java @@ -57,32 +57,36 @@ public class TBRedisStandaloneConfiguration extends TBRedisCacheConfiguration { @Value("${redis.password:}") private String password; + @Value("${redis.ssl.enabled:}") + private boolean useSsl; + public JedisConnectionFactory loadFactory() { RedisStandaloneConfiguration standaloneConfiguration = new RedisStandaloneConfiguration(); standaloneConfiguration.setHostName(host); standaloneConfiguration.setPort(port); standaloneConfiguration.setDatabase(db); standaloneConfiguration.setPassword(password); - if (useDefaultClientConfig) { - return new JedisConnectionFactory(standaloneConfiguration); - } else { - return new JedisConnectionFactory(standaloneConfiguration, buildClientConfig()); - } + return new JedisConnectionFactory(standaloneConfiguration, buildClientConfig()); } private JedisClientConfiguration buildClientConfig() { - if (usePoolConfig) { - return JedisClientConfiguration.builder() + JedisClientConfiguration.JedisClientConfigurationBuilder jedisClientConfigurationBuilder = JedisClientConfiguration.builder(); + if (!useDefaultClientConfig) { + jedisClientConfigurationBuilder .clientName(clientName) .connectTimeout(Duration.ofMillis(connectTimeout)) - .readTimeout(Duration.ofMillis(readTimeout)) - .usePooling().poolConfig(buildPoolConfig()) - .build(); - } else { - return JedisClientConfiguration.builder() - .clientName(clientName) - .connectTimeout(Duration.ofMillis(connectTimeout)) - .readTimeout(Duration.ofMillis(readTimeout)).build(); + .readTimeout(Duration.ofMillis(readTimeout)); } + if (useSsl) { + jedisClientConfigurationBuilder + .useSsl() + .sslSocketFactory(createSslSocketFactory()); + } + if (usePoolConfig) { + jedisClientConfigurationBuilder + .usePooling() + .poolConfig(buildPoolConfig()); + } + return jedisClientConfigurationBuilder.build(); } } \ No newline at end of file diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSslCredentialsConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSslCredentialsConfiguration.java new file mode 100644 index 0000000000..fa0c09043d --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbRedisSslCredentialsConfiguration.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2023 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.cache; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "redis.ssl") +@Data +public class TbRedisSslCredentialsConfiguration { + + private boolean enabled; + + private String truststoreLocation; + + private String truststorePassword; + + private String keystoreLocation; + + private String keystorePassword; +}