Skip to content

CAMEL-23768: camel-keycloak - select the JWKS verification key by the token kid#24527

Merged
oscerd merged 1 commit into
mainfrom
fix/CAMEL-23768
Jul 8, 2026
Merged

CAMEL-23768: camel-keycloak - select the JWKS verification key by the token kid#24527
oscerd merged 1 commit into
mainfrom
fix/CAMEL-23768

Conversation

@oscerd

@oscerd oscerd commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

Fixes CAMEL-23768.

Problem

KeycloakPublicKeyResolver ignored the JWT header kid and returned the first key in the JWKS, and KeycloakSecurityProcessor never passed the kid through (getPublicKey(null)). During key rotation (multiple keys present) this could pick the wrong key and reject an otherwise-valid token. The token is still cryptographically verified against a real key, so this is a correctness/availability matter, not a bypass.

Change

  • KeycloakSecurityHelper.extractKeyId(token) — pulls the kid from the JWS header using Keycloak's own JWSInput (existing dependency); returns null if absent/unparseable.
  • KeycloakSecurityProcessor — extracts the kid and passes it to resolver.getPublicKey(kid).
  • KeycloakPublicKeyResolver.getPublicKey — forces one refresh when the kid is unknown (to pick up rotated keys), then delegates to a new package-visible selectKey that fails closed (throws) when a kid is given but no key matches. The first-key fallback is kept only when the token carries no kid.

Tests

  • KeycloakPublicKeyResolverTest — matching kid selects that key (not the first); unknown kid fails closed; null kid falls back to the first key (JWKS built from generated RSA keys, no container).
  • KeycloakSecurityHelperTestextractKeyId returns the header kid / null for a malformed token.
  • Full reactor build (mvn clean install -DskipTests, 1871 modules) green; no generated-file impact (plain Java).

Compatibility / backport

Correctness fix, fixVersions 4.18.4 / 4.22.0 (camel-keycloak does not exist on the 4.14.x line). Default behaviour for a single-key JWKS is unchanged.


Claude Code on behalf of Andrea Cosentino

@oscerd oscerd requested review from davsclaus and gnodet July 8, 2026 10:09
@davsclaus

Copy link
Copy Markdown
Contributor

there is a merge conflict

@davsclaus davsclaus 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.

Review: CAMEL-23768 — Select JWKS verification key by token kid

No blocking issues. Clean correctness fix for a real key-rotation problem.

What was checked

  • Git history: KeycloakPublicKeyResolver introduced in CAMEL-22854 (c1ed776) by the same author. The getPublicKey(null) call has been there since inception — this fixes an original oversight, not reverting intentional behavior.
  • Correctness: selectKey(kid) throws IOException when a kid is given but no key matches. Right security posture: never verify a token against an unrelated key during rotation.
  • Backward compatibility: When the token carries no kid (null), the first-key fallback is preserved. Single-key JWKS deployments are unaffected.
  • Tests: Good coverage of the three key scenarios (matching kid, unknown kid fails closed, null kid fallback). Tests build their own JWKS from generated RSA keys — no container needed.
  • AI attribution: Present in both the PR body and the commit trailer.

Minor observations (non-blocking)

  • getPublicKey() can call refreshKeys() twice in quick succession (cache expiry + unknown kid). Harmless (idempotent, synchronized) but costs an extra HTTP round-trip in that edge case.
  • parseJwks widened from private to package-visible for testability. Common pattern.
  • Broad Exception catch in extractKeyId is pragmatic since JWSInput can throw various exceptions from untrusted input, and a null return is safe because TokenVerifier rejects bad tokens downstream.

This review was generated by an AI agent and may contain inaccuracies. Please verify all suggestions before applying.

… token kid

KeycloakPublicKeyResolver ignored the JWT header kid and returned the first key in
the JWKS, and KeycloakSecurityProcessor never passed the kid through
(getPublicKey(null)). During key rotation (multiple keys present) this could pick
the wrong key and reject an otherwise-valid token. The token is still
cryptographically verified against a real key, so this is a correctness/availability
matter, not a bypass.

The processor now extracts the kid from the token header (KeycloakSecurityHelper
.extractKeyId via Keycloak's JWSInput) and passes it to the resolver. getPublicKey
forces a single refresh when the kid is unknown (to pick up rotated keys) and fails
closed via a new selectKey helper when a kid is given but no key matches; the
first-key fallback is kept only when the token carries no kid.

Adds KeycloakPublicKeyResolverTest (matching kid / fail-closed on unknown kid /
first-key fallback for null kid) and extractKeyId coverage in KeycloakSecurityHelperTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@oscerd

oscerd commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Rebased onto main and resolved the conflict. It was with CAMEL-23875 (optional audience validation), which added @Test methods at the same spot in KeycloakSecurityHelperTest; the two additions are independent, so I kept both — the audience tests and the new extractKeyId tests now coexist. The production files (KeycloakSecurityHelper, KeycloakSecurityProcessor) merged automatically (kid-selection and audience validation are orthogonal). All camel-keycloak unit tests pass (HelperTest 23, ResolverTest 3, ProcessorTest 9) and a full reactor build is green. CI is re-running.

Claude Code on behalf of Andrea Cosentino

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🌟 Thank you for your contribution to the Apache Camel project! 🌟
🤖 CI automation will test this PR automatically.

🐫 Apache Camel Committers, please review the following items:

  • First-time contributors require MANUAL approval for the GitHub Actions to run
  • You can use the command /component-test (camel-)component-name1 (camel-)component-name2.. to request a test from the test bot although they are normally detected and executed by CI.
  • You can label PRs using skip-tests and test-dependents to fine-tune the checks executed by this PR.
  • Build and test logs are available in the summary page. Only Apache Camel committers have access to the summary.

⚠️ Be careful when sharing logs. Review their contents before sharing them publicly.

@github-actions

github-actions Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

🧪 CI tested the following changed modules:

  • components/camel-keycloak

🔬 Scalpel shadow comparison — Scalpel: 9 tested, 29 compile-only — current: 9 all tested

Maveniverse Scalpel detected 38 affected modules (current approach: 9).

⚠️ Modules only in Scalpel (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

Skip-tests mode would test 9 modules (1 direct + 8 downstream), skip tests for 29 (generated code, meta-modules)

Modules Scalpel would test (9)
  • camel-jbang-mcp
  • camel-jbang-plugin-mcp
  • camel-jbang-plugin-route-parser
  • camel-jbang-plugin-tui
  • camel-jbang-plugin-validate
  • camel-keycloak
  • camel-launcher-container
  • camel-yaml-dsl-validator
  • camel-yaml-dsl-validator-maven-plugin
Modules with tests skipped (29)
  • apache-camel
  • camel-allcomponents
  • camel-catalog
  • camel-catalog-console
  • camel-catalog-lucene
  • camel-catalog-maven
  • camel-catalog-suggest
  • camel-componentdsl
  • camel-csimple-maven-plugin
  • camel-endpointdsl
  • camel-endpointdsl-support
  • camel-itest
  • camel-jbang-core
  • camel-jbang-it
  • camel-jbang-main
  • camel-jbang-plugin-edit
  • camel-jbang-plugin-generate
  • camel-jbang-plugin-kubernetes
  • camel-jbang-plugin-test
  • camel-kamelet-main
  • camel-launcher
  • camel-report-maven-plugin
  • camel-route-parser
  • camel-yaml-dsl
  • camel-yaml-dsl-deserializers
  • camel-yaml-dsl-maven-plugin
  • coverage
  • docs
  • dummy-component

ℹ️ Shadow mode — Scalpel observes but does not affect test execution. Learn more

All tested modules (38 modules)
  • Camel :: All Components Sync point
  • Camel :: Assembly
  • Camel :: Catalog :: CSimple Maven Plugin (deprecated)
  • Camel :: Catalog :: Camel Catalog
  • Camel :: Catalog :: Camel Report Maven Plugin
  • Camel :: Catalog :: Camel Route Parser
  • Camel :: Catalog :: Console
  • Camel :: Catalog :: Dummy Component
  • Camel :: Catalog :: Lucene (deprecated)
  • Camel :: Catalog :: Maven
  • Camel :: Catalog :: Suggest
  • Camel :: Component DSL
  • Camel :: Coverage
  • Camel :: Docs
  • Camel :: Endpoint DSL
  • Camel :: Endpoint DSL :: Support
  • Camel :: Integration Tests
  • Camel :: JBang :: Core
  • Camel :: JBang :: Integration tests
  • Camel :: JBang :: MCP
  • Camel :: JBang :: Main
  • Camel :: JBang :: Plugin :: Edit
  • Camel :: JBang :: Plugin :: Generate
  • Camel :: JBang :: Plugin :: Kubernetes
  • Camel :: JBang :: Plugin :: MCP
  • Camel :: JBang :: Plugin :: Route Parser
  • Camel :: JBang :: Plugin :: TUI
  • Camel :: JBang :: Plugin :: Testing
  • Camel :: JBang :: Plugin :: Validate
  • Camel :: Kamelet Main
  • Camel :: Keycloak
  • Camel :: Launcher
  • Camel :: Launcher :: Container
  • Camel :: YAML DSL
  • Camel :: YAML DSL :: Deserializers
  • Camel :: YAML DSL :: Maven Plugins
  • Camel :: YAML DSL :: Validator
  • Camel :: YAML DSL :: Validator Maven Plugin

⚙️ View full build and test results

@oscerd oscerd merged commit f4c1902 into main Jul 8, 2026
5 checks passed
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