fix(cache): apply cache.redis.password and redis.use.ssl on every Redis connection#2874
Merged
simonredfern merged 2 commits intoJul 17, 2026
Merged
Conversation
The memoize-backed cache (memoizeWithRedis / memoizeSyncWithRedis, the backing store for Caching.memoize*WithProvider) was built with RedisCache(url, port), which constructs its own internal JedisPool that ignores cache.redis.password and redis.use.ssl. Every other Redis path already leases from the configured jedisPool, which passes both. With requirepass enabled on Redis, the memoize path failed with NOAUTH while the startup health check (which uses jedisPool) still reported PASSED, so the main caching layer went dead with clean-looking logs. Reuse the already authenticated, optionally SSL-configured jedisPool for the memoize store too; the RedisCache(JedisPool, Option[ClassLoader]) overload resolves with its default second argument. Also document cache.redis.password and redis.use.ssl in the Redis block of sample.props.template, and note that the 127.0.0.1 default is a deliberate security default (Redis must not be bound to a public interface).
The chat, log-cache and metrics event buses each built their subscriber connection with the plain Jedis(url, port, timeout) constructor, which is unencrypted. They authenticated explicitly, so cache.redis.password was applied, but redis.use.ssl was silently ignored: with TLS enabled the pool and the memoize cache spoke TLS while these three streaming buses tried to connect in plaintext, so they either failed to connect or would have sent data unencrypted on a link the operator configured as TLS-only. Subscribers genuinely cannot lease from jedisPool, since subscribe/psubscribe occupy their connection for the life of the subscription. Rather than let each call site reassemble the connection settings, add Redis.newSubscriberConnection() which applies the same password and TLS configuration the pool uses, and have the three buses call it. The SSLContext becomes a lazy val so the keystore and truststore are read once and only when redis.use.ssl is on. Verified against a TLS-only, requirepass-protected local Redis: the pre-fix plaintext construction fails with a connection reset, while a subscriber from newSubscriberConnection() authenticates and completes a pub/sub round trip. The no-SSL path is unchanged and still exercised by the existing cache tests.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Problem
code/api/cache/Redis.scalareadscache.redis.passwordandredis.use.ssl, but not every connection path applied them. Two gaps, both of which only surface once an operator actually hardens Redis:1. The memoize cache ignored the password (and TLS).
jedisPoolpasses both settings, and is what rate limiting, idempotency, the health check andRedis.uselease from. But the memoize store was built separately:scalacache-redis0.9.3'sRedisCache.apply(String, int)constructs its own internalJedisPoolwith no password and no SSL. That store backsmemoizeWithRedis/memoizeSyncWithRedis, which serveCaching.memoizeWithProvider/memoizeSyncWithProvider— the main API caching layer, including connector result caching (31 call sites across 22 files).With
requirepassenabled, that path fails withNOAUTHwhileperformStartupHealthCheck()— which leases fromjedisPool— still logsRedis health check PASSED. The failure surfaces only as cache misses and a performance collapse, with clean-looking logs.2. The pub/sub subscribers ignored TLS.
ChatEventBus,LogCacheEventBusandMetricsEventBuseach built their subscriber connection with the plaintextJedis(url, port, timeout)constructor. They authenticated explicitly, so the password was applied, butredis.use.sslwas silently dropped: with TLS on, the pool and the memoize cache spoke TLS while these three streaming buses tried to connect in plaintext.Fix
Memoize store — reuse the pool that already has both settings:
The
RedisCache(JedisPool, Option[ClassLoader])overload resolves with its default second argument.Subscribers — they genuinely cannot lease from
jedisPool, sincesubscribe/psubscribeoccupy their connection for the life of the subscription. Rather than have each call site reassemble the connection settings, addRedis.newSubscriberConnection(), which applies the same password and TLS configuration the pool uses, and have the three buses call it.sslContextbecomes alazy valso the keystore and truststore are read once, and only whenredis.use.sslis on.Docs —
sample.props.templatenow listsredis.use.sslalongsidecache.redis.passwordin the Redis block, and notes that the127.0.0.1default is a deliberate security default: Redis should not be bound to a public interface.Verification
Against a local
requirepass-protected Redis:NOAUTH)Against a local TLS-only,
requirepass-protected Redis (self-signed CA; the port rejects plaintext):JedisConnectionException: Connection resetNo-password and no-SSL defaults are unchanged: a null password still yields an unauthenticated connection, and
MethodRoutingCacheInvalidationTest+RedisDeserializeMissTest(5 tests, exercising the memoize path against a passwordless Redis) pass.All connection construction sites were audited (
new JedisPool,new Jedis,RedisCache(,JedisPubSub): the pool, the memoize store, the three subscribers, andRedisMessaging/RedisLogger/ConnectorMetricsRedis(all of which already go through the pool). The cache-config endpoint exposes onlyurl,portanduse_ssl— never the password.CI is green on both commits (9 test shards + compile + docker).