Skip to content

fix(cache): apply cache.redis.password and redis.use.ssl on every Redis connection#2874

Merged
simonredfern merged 2 commits into
OpenBankProject:developfrom
hongwei1:fix/redis-password-and-ssl
Jul 17, 2026
Merged

fix(cache): apply cache.redis.password and redis.use.ssl on every Redis connection#2874
simonredfern merged 2 commits into
OpenBankProject:developfrom
hongwei1:fix/redis-password-and-ssl

Conversation

@hongwei1

Copy link
Copy Markdown
Contributor

Problem

code/api/cache/Redis.scala reads cache.redis.password and redis.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).

jedisPool passes both settings, and is what rate limiting, idempotency, the health check and Redis.use lease from. But the memoize store was built separately:

implicit val scalaCache = ScalaCache(RedisCache(url, port))

scalacache-redis 0.9.3's RedisCache.apply(String, int) constructs its own internal JedisPool with no password and no SSL. That store backs memoizeWithRedis / memoizeSyncWithRedis, which serve Caching.memoizeWithProvider / memoizeSyncWithProvider — the main API caching layer, including connector result caching (31 call sites across 22 files).

With requirepass enabled, that path fails with NOAUTH while performStartupHealthCheck() — which leases from jedisPool — still logs Redis 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, LogCacheEventBus and MetricsEventBus each built their subscriber connection with the plaintext Jedis(url, port, timeout) constructor. They authenticated explicitly, so the password was applied, but redis.use.ssl was 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:

implicit val scalaCache = ScalaCache(RedisCache(jedisPool))

The RedisCache(JedisPool, Option[ClassLoader]) overload resolves with its default second argument.

Subscribers — they genuinely cannot lease from jedisPool, since subscribe/psubscribe occupy their connection for the life of the subscription. Rather than have 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. sslContext becomes a lazy val so the keystore and truststore are read once, and only when redis.use.ssl is on.

Docssample.props.template now lists redis.use.ssl alongside cache.redis.password in the Redis block, and notes that the 127.0.0.1 default is a deliberate security default: Redis should not be bound to a public interface.

Verification

Against a local requirepass-protected Redis:

Scenario Before After
memoize round trip never caches — every call recomputes, 0 keys written (silent NOAUTH) caches; second call served from Redis

Against a local TLS-only, requirepass-protected Redis (self-signed CA; the port rejects plaintext):

Scenario Before After
subscriber connect + pub/sub round trip JedisConnectionException: Connection reset authenticates and delivers the message

No-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, and RedisMessaging / RedisLogger / ConnectorMetricsRedis (all of which already go through the pool). The cache-config endpoint exposes only url, port and use_ssl — never the password.

CI is green on both commits (9 test shards + compile + docker).

hongwei1 added 2 commits July 17, 2026 10:45
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.
@sonarqubecloud

Copy link
Copy Markdown

@simonredfern
simonredfern merged commit cd55cec into OpenBankProject:develop Jul 17, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants