diff --git a/obp-api/src/main/resources/props/sample.props.template b/obp-api/src/main/resources/props/sample.props.template index 12356d2cb6..c5ac75d4c4 100644 --- a/obp-api/src/main/resources/props/sample.props.template +++ b/obp-api/src/main/resources/props/sample.props.template @@ -1085,10 +1085,14 @@ featured_apis=elasticSearchWarehouseV300 ## Note: You do NOT need to include anything here for this to work. # -- Redis cache ------------------------------------- +# The 127.0.0.1 default is a deliberate security default: Redis must not be bound to a +# public interface. If you enable authentication (requirepass) or TLS on Redis, set the +# properties below - they are honoured by both the direct and the memoize-backed cache paths. # cache.redis.url=127.0.0.1 # cache.redis.port=6379 # Default value is empty or omitted props # cache.redis.password = +# redis.use.ssl=false # --------------------------------------------------------- # -- New Style Endpoints ----------------------- diff --git a/obp-api/src/main/scala/code/api/cache/Redis.scala b/obp-api/src/main/scala/code/api/cache/Redis.scala index 74c37e69bd..99ef445cce 100644 --- a/obp-api/src/main/scala/code/api/cache/Redis.scala +++ b/obp-api/src/main/scala/code/api/cache/Redis.scala @@ -46,16 +46,35 @@ object Redis extends MdcLoggable { poolConfig.setNumTestsPerEvictionRun(3) poolConfig.setBlockWhenExhausted(true) + // Lazy so the keystore/truststore files are only read when redis.use.ssl is on, and only once + // even though both jedisPool and every subscriber connection need the socket factory. + private lazy val sslContext: SSLContext = configureSslContext() + val jedisPool = if (useSsl) { // SSL connection: Use SSLContext with JedisPool - val sslContext = configureSslContext() new JedisPool(poolConfig, url, port, timeout, password, true, sslContext.getSocketFactory, null, null) } else { // Non-SSL connection new JedisPool(poolConfig, url, port, timeout, password) } + /** + * Build a dedicated, non-pooled connection for a pub/sub subscriber. + * + * `subscribe`/`psubscribe` occupy their connection for the whole life of the subscription, + * so subscribers cannot lease from jedisPool. They must still apply the same password and + * TLS settings the pool uses: the plain `Jedis(url, port, timeout)` constructor is + * unencrypted, so building one directly silently ignores redis.use.ssl. + */ + def newSubscriberConnection(): Jedis = { + val jedis = + if (useSsl) new Jedis(url, port, timeout, true, sslContext.getSocketFactory, null, null) + else new Jedis(url, port, timeout) + if (password != null) jedis.auth(password) + jedis + } + // Redis startup health check private def performStartupHealthCheck(): Unit = { try { @@ -290,7 +309,11 @@ object Redis extends MdcLoggable { } } - implicit val scalaCache = ScalaCache(RedisCache(url, port)) + // Reuse the pool built above so the memoize-backed cache shares the same authenticated, + // optionally SSL-configured connection. The RedisCache(url, port) overload builds its own + // JedisPool internally with no password and no SSL, so with `requirepass` enabled it fails + // with NOAUTH while the jedisPool-based paths keep working. + implicit val scalaCache = ScalaCache(RedisCache(jedisPool)) implicit val flags = Flags(readsEnabled = true, writesEnabled = true) implicit def anyToByte[T](implicit m: Manifest[T]) = new Codec[T, Array[Byte]] { diff --git a/obp-api/src/main/scala/code/chat/ChatEventBus.scala b/obp-api/src/main/scala/code/chat/ChatEventBus.scala index ab799f3a7f..cbac73654d 100644 --- a/obp-api/src/main/scala/code/chat/ChatEventBus.scala +++ b/obp-api/src/main/scala/code/chat/ChatEventBus.scala @@ -123,8 +123,7 @@ object ChatEventBus extends MdcLoggable { subscriberThread = new Thread(() => { try { // Dedicated connection for the subscriber (not from the pool) - subscriberJedis = new Jedis(Redis.url, Redis.port, Redis.timeout) - if (Redis.password != null) subscriberJedis.auth(Redis.password) + subscriberJedis = Redis.newSubscriberConnection() logger.info(s"ChatEventBus says: Redis subscriber started, pattern-subscribing to ${CHANNEL_PREFIX}*") subscriberJedis.psubscribe(pubSub, s"${CHANNEL_PREFIX}*") } catch { diff --git a/obp-api/src/main/scala/code/logcache/LogCacheEventBus.scala b/obp-api/src/main/scala/code/logcache/LogCacheEventBus.scala index 8e5ff1844c..de12f9a35f 100644 --- a/obp-api/src/main/scala/code/logcache/LogCacheEventBus.scala +++ b/obp-api/src/main/scala/code/logcache/LogCacheEventBus.scala @@ -107,8 +107,7 @@ object LogCacheEventBus extends MdcLoggable { subscriberThread = new Thread(() => { try { - subscriberJedis = new Jedis(Redis.url, Redis.port, Redis.timeout) - if (Redis.password != null) subscriberJedis.auth(Redis.password) + subscriberJedis = Redis.newSubscriberConnection() logger.info(s"LogCacheEventBus says: Redis subscriber started, pattern-subscribing to ${CHANNEL_PREFIX}*") subscriberJedis.psubscribe(pubSub, s"${CHANNEL_PREFIX}*") } catch { diff --git a/obp-api/src/main/scala/code/metricsstream/MetricsEventBus.scala b/obp-api/src/main/scala/code/metricsstream/MetricsEventBus.scala index 42255ccd32..41a52df5a0 100644 --- a/obp-api/src/main/scala/code/metricsstream/MetricsEventBus.scala +++ b/obp-api/src/main/scala/code/metricsstream/MetricsEventBus.scala @@ -102,8 +102,7 @@ object MetricsEventBus extends MdcLoggable { subscriberThread = new Thread(() => { try { - subscriberJedis = new Jedis(Redis.url, Redis.port, Redis.timeout) - if (Redis.password != null) subscriberJedis.auth(Redis.password) + subscriberJedis = Redis.newSubscriberConnection() logger.info(s"MetricsEventBus says: Redis subscriber started, subscribing to $ALL_CHANNEL") subscriberJedis.subscribe(pubSub, ALL_CHANNEL) } catch {