Skip to content

security: cache SCRAM password validation#31054

Open
nguyen-andrew wants to merge 4 commits into
redpanda-data:devfrom
nguyen-andrew:scram-memo
Open

security: cache SCRAM password validation#31054
nguyen-andrew wants to merge 4 commits into
redpanda-data:devfrom
nguyen-andrew:scram-memo

Conversation

@nguyen-andrew

@nguyen-andrew nguyen-andrew commented Jul 8, 2026

Copy link
Copy Markdown
Member

HTTP Basic auth (PandaProxy, Schema Registry, Admin API) and Kafka SASL/PLAIN
rederive the credential's stored SCRAM key on every request: a PBKDF2-style
loop of >=4096 HMAC rounds, run on-reactor before the body is parsed. Under
high-rate authenticated traffic this dominates CPU. A cluster serving a few
hundred HTTP-produce req/s per shard through PandaProxy saturated its reactor
core almost entirely in this path (~45% of on-CPU time in
validate_scram_credential), starving heartbeats.

This memoizes the derivation with a bounded per-shard cache, keyed by an HMAC
of (algorithm, password, salt, iterations) and holding only the derived stored
key (already in the credential store). It is purely functional, so a password
change re-salts and re-keys and entries never go stale, and it is gated by the
scram_credential_cache_enabled cluster config (default on) as a kill switch.

Fixes CORE-16829

Microbench (--config=release, single core)

Repeat = same credential per call (steady-state auth); unique = fresh password
per call (cache-miss path).

Validation case Before After Speedup
SHA-256, correct password (repeat) 391 µs 558 ns ~700x
SHA-256, wrong password (repeat) 392 µs 568 ns ~690x
SHA-512, correct password (repeat) 1.04 ms 554 ns ~1,880x
SHA-256, unique password (cache miss) 390 µs 396 µs unchanged

End-to-end (single core, unbatched ~2 KiB HTTP produce)

Scenario Before After
Basic-auth produce 288 req/s, 3.43 ms/req 635 req/s, 1.56 ms/req
No-auth baseline (same path, no auth) 589 req/s, 1.68 ms/req 660 req/s, 1.50 ms/req
Basic-auth, 50% empty-body requests 336 req/s, 2.96 ms/req 1,354 req/s, 0.74 ms/req

After the fix, Basic-auth throughput matches the no-auth baseline. Disabling the
cache (scram_credential_cache_enabled=false) drops it back to 298 req/s,
confirming the kill switch reproduces pre-fix behavior.

Backports Required

  • v26.1.x
  • v25.3.x
  • v25.2.x

Release Notes

  • none

Measures the per-call cost of the SCRAM password check that HTTP Basic
auth runs on every pandaproxy/schema registry/admin request. The salted
password is rederived per request (4096 HMAC rounds), which saturated
pandaproxy CPU under unbatched HTTP produce in a production incident:
~473us per SHA-256 validation, so a few thousand requests per second
exhaust a core before any request work happens. Covers the wrong
password (reject) path, which costs the same as an accept, SHA-512
(~3x SHA-256), and a unique-password case that always pays the full
derivation.
Exposes the password -> stored_key derivation as a reusable step so a
subsequent commit can memoize it.
Copilot AI review requested due to automatic review settings July 8, 2026 21:26
@nguyen-andrew nguyen-andrew requested a review from a team as a code owner July 8, 2026 21:26

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a per-shard memoization layer for SCRAM password validation to eliminate repeated PBKDF2-style derivations on hot authentication paths (HTTP Basic auth and SASL/PLAIN), controlled via a new cluster configuration kill switch.

Changes:

  • Add security::scram_credential_cache and integrate it into validate_scram_credential with a thread_local per-shard cache gated by scram_credential_cache_enabled.
  • Refactor SCRAM algorithm code to expose derive_stored_key() and reuse it in password validation.
  • Add unit tests and an rpbench benchmark covering cached vs uncached behavior and correctness.

Reviewed changes

Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/v/security/tests/scram_credential_cache_test.cc New unit tests validating cache hit/miss behavior, key composition, eviction bounds, and cached credential validation.
src/v/security/tests/scram_credential_bench.cc New perf benchmark exercising repeated/unique password validation cases to quantify caching impact.
src/v/security/tests/scram_algorithm_test.cc Adds a test ensuring derive_stored_key() matches stored keys produced by credential creation.
src/v/security/tests/BUILD Wires new test and benchmark targets into the build.
src/v/security/scram_credential_cache.h Declares the per-shard SCRAM derivation cache and its keying/metrics interface.
src/v/security/scram_credential_cache.cc Implements HMAC-keyed cache entries backed by utils::chunked_kv_cache.
src/v/security/scram_authenticator.h Documents cached validation behavior and introduces a detail::validate_scram_credential overload for caller-provided caches.
src/v/security/scram_authenticator.cc Implements cached derivation path and config-gated use of a per-thread cache.
src/v/security/scram_algorithm.h Adds derive_stored_key() and refactors validate_password() to use it.
src/v/security/BUILD Adds the new cache implementation to the security library and links required deps.
src/v/config/configuration.h Declares the new scram_credential_cache_enabled configuration property.
src/v/config/configuration.cc Defines the new configuration property and its description/default.

Comment thread src/v/security/scram_credential_cache.cc Outdated
Comment thread src/v/security/scram_credential_cache.h Outdated
Per-shard memoization of SCRAM password derivations, keyed by an HMAC
(random per-instance key) of (mechanism, password, salt, iterations) so
plaintext passwords are not retained, holding only the derived stored
key: the same datum already persisted in the credential store. The
mapping is purely functional, so entries never go stale: updating a
credential generates a fresh salt, which changes the cache key. Built on
the S3-FIFO chunked_kv_cache, whose scan resistance keeps one-shot
wrong-password entries from displacing hot legitimate ones.
validate_scram_credential now consults a shard-local
scram_credential_cache, skipping the salted password derivation for
repeat authentications. HTTP Basic auth (pandaproxy, schema registry,
admin API) and SASL/PLAIN pay that derivation on every request; in
production, unbatched HTTP produce through pandaproxy at a few hundred
requests per second saturated a core on it. Cache hits skip the
derivation entirely; the uncached path is unchanged.

The scram_credential_cache_enabled cluster config (default true) is an
operational kill switch.
@nguyen-andrew

Copy link
Copy Markdown
Member Author

Force push to address Copilot comments.

@nguyen-andrew nguyen-andrew self-assigned this Jul 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants