Skip to content

alternative fix: client secret re-encoding#3965

Merged
strehle merged 11 commits into
cloudfoundry:developfrom
gdgenchev:fix-password-encoder
Jun 29, 2026
Merged

alternative fix: client secret re-encoding#3965
strehle merged 11 commits into
cloudfoundry:developfrom
gdgenchev:fix-password-encoder

Conversation

@gdgenchev

@gdgenchev gdgenchev commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

This basically restores pre spring security 7 behaviour across all components that use a caching/non-caching password encoder bean. It reuses the empty aware delegating password encoder that was introduced in spring boot 4 migration, but it was used just in one place.

Alternative for #3962

@gdgenchev gdgenchev changed the title make the root nonCachingPasswordEncoder bean empty aware fix: make the root nonCachingPasswordEncoder bean empty aware Jun 25, 2026
@strehle strehle requested a review from Copilot June 25, 2026 09:07

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.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@strehle

strehle commented Jun 25, 2026

Copy link
Copy Markdown
Member

thanks, there is already another PR #3962

we will handle the fix for it with prio

@strehle strehle requested a review from a team June 25, 2026 09:09
@gdgenchev gdgenchev changed the title fix: make the root nonCachingPasswordEncoder bean empty aware alternative fix: client secret re-encoding Jun 25, 2026
@jochenehret

Copy link
Copy Markdown

Confirmed: This fix resolves the issue. The bbr-run-drats jobs was green:
https://concourse.wg-ard.ci.cloudfoundry.org/teams/main/pipelines/cf-deployment/jobs/bbr-run-drats/builds/2456

strehle
strehle previously approved these changes Jun 25, 2026
@github-project-automation github-project-automation Bot moved this from Inbox to Pending Merge | Prioritized in Foundational Infrastructure Working Group Jun 25, 2026
@strehle strehle requested review from duanemay and fhanik June 25, 2026 11:40
@jochenehret

Copy link
Copy Markdown

Is there a regression test for this? For a client with empty secret, the test should verify that the secret hash doesn't change after a uaa restart.

The test source set previously defined a class with the same fully-qualified
name as the production PasswordEncoderConfig, shadowing it on the test
classpath. Tests that instantiated `new PasswordEncoderConfig()` got the test
version rather than the production one, hiding production-bean changes from
test coverage. Renaming removes the shadow.
Pins matches("", encode("")) on the production nonCachingPasswordEncoder bean
and on the cachingPasswordEncoder bean wired in OauthEndpointBeanConfiguration.
Spring Security 7's BCryptPasswordEncoder rejects empty rawPassword, so both
beans must wrap a bcrypt encoder with empty-secret awareness; otherwise the cf
CLI empty-secret client breaks (DRATs v79.0.0 failure).
@gdgenchev gdgenchev force-pushed the fix-password-encoder branch from c8e86b8 to 8844844 Compare June 25, 2026 13:28
@strehle

strehle commented Jun 25, 2026

Copy link
Copy Markdown
Member

@gdgenchev Ok, thank you please fix the tests. we want your PR to get merged :-)

duanemay added 3 commits June 25, 2026 15:44
Fix timing attack vulnerability in password encoders by replacing
standard string comparison with MessageDigest.isEqual() for
constant-time comparison.
Extract duplicated constantTimeEquals method from password encoders
duanemay
duanemay previously approved these changes Jun 25, 2026
@gdgenchev

gdgenchev commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

@duanemay, sick! This was bugging me for hours. I was trying to fix it only test side, but the shadowing PasswordEncoderConfig in tests was causing a lot of trouble.

@duanemay

Copy link
Copy Markdown
Member

@gdgenchev after a review I can merge

@gdgenchev

gdgenchev commented Jun 25, 2026

Copy link
Copy Markdown
Contributor Author

I am checking again this @DefaultTestContext - seems with the current change, the encoder bean in those tests will not get overridden to the noop one from test config (but the prod one will be used), which might affect performance. I am trying to add the PasswordEncoderTestConfig to the DefaultTestContext, but I need to remove @DefaultTestContext from some @Nested test classes as it causes issues. Will try.

strehle
strehle previously approved these changes Jun 26, 2026
@gdgenchev

gdgenchev commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

I tested and indeed before the rename of the shadowed test class PasswordEncoderConfig, it was using the test one and now it uses the prod one (I checked the info log that both bean methods have, seems they are there for this purpose). I am struggling to make this work. Seems indeed unit tests take 22m in this PR and in other 12m, so we might have hit the performance.

Introduces a dedicated Gradle source set (prodWiringTest) that excludes
test-scope class shadows, allowing wiring pin tests to instantiate the
real PasswordEncoderConfig rather than the noop test double. Restores
PasswordEncoderConfigTest and OauthEndpointBeanConfigurationTest in this
source set, adds Javadoc to PasswordEncoderConfig explaining why
EmptyAwareDelegatingPasswordEncoder must remain the outermost wrapper,
and wires prodWiringTest into the test task so CI picks it up.
@gdgenchev gdgenchev dismissed stale reviews from strehle and duanemay via 45abdf3 June 26, 2026 08:42
@gdgenchev

gdgenchev commented Jun 26, 2026

Copy link
Copy Markdown
Contributor Author

This is the best I could achieve with smallest change. Unit tests are back to 10 min.

Other approaches caused a lot of issues - i guess it could be possible to somehow rename the shadowing test config class and adapt, but that led to a lot of failing tests.

Thus, decided to keep the shadowing class and just focus on a way to achieve the new tests - and this was possible through gradle configuration and introducing new source set with prod classpath (so the shadowing class in tests does not overwrite the prod one). Seems a bit weird, but it is the simplest and best-working approach I found - now the new regression tests run as part of the integration tests - I have seen them red without the empty aware wrapper.

…runs

ClientAdminBootstrapProdEncoderTest runs in the prodWiringTest source set
(real prod encoder, real DB, no test-scope shadows) and calls
afterPropertiesSet() twice to simulate a UAA restart, asserting the stored
hash is identical. This is the exact scenario that caused the v79.0.0 DRAT
failure: without EmptyAwareDelegatingPasswordEncoder, Spring Security 7's
BCryptPasswordEncoder rejects empty rawPassword in matches(), causing
ClientAdminBootstrap to re-encode empty-secret clients on every startup
and invalidating existing tokens.
…Gradle config

Renames the source set and task from prodWiringTest to integrationTest,
following the Gradle convention. Uses sourceSets["integrationTest"] directly
instead of a local variable, and wires into check rather than test so that
unit and integration tests remain separate tasks.
@oliver-heinrich

Copy link
Copy Markdown

Hi @gdgenchev & @strehle the fix is reviewed and tested. Can you merge it and create a new UAA release as the issue is currently blocking the new CF release.

strehle
strehle previously approved these changes Jun 29, 2026
strehle
strehle previously approved these changes Jun 29, 2026
The test did not measure timing; rename it to reflect that it verifies
empty raw passwords do not match a {noop} prefix with a non-empty value,
and drop the comments that admitted no timing was tested.

minor reviews
@strehle strehle merged commit 977ebca into cloudfoundry:develop Jun 29, 2026
32 of 33 checks passed
@github-project-automation github-project-automation Bot moved this from Pending Merge | Prioritized to Done in Foundational Infrastructure Working Group Jun 29, 2026
@strehle

strehle commented Jun 29, 2026

Copy link
Copy Markdown
Member

Hi @gdgenchev & @strehle the fix is reviewed and tested. Can you merge it and create a new UAA release as the issue is currently blocking the new CF release.

New UAA should appear in cf deployment next hours

@oliver-heinrich

Copy link
Copy Markdown

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

6 participants