Skip to content

lore-server: Fix JWK refresh cache check - #99

Open
e345ee wants to merge 1 commit into
EpicGames:mainfrom
e345ee:fix-jwk-cache-refresh
Open

lore-server: Fix JWK refresh cache check#99
e345ee wants to merge 1 commit into
EpicGames:mainfrom
e345ee:fix-jwk-cache-refresh

Conversation

@e345ee

@e345ee e345ee commented Jul 3, 2026

Copy link
Copy Markdown

What

Fix the JWK cache short-circuit so fetch_new_keys(Some(kid)) skips the network fetch only when the requested kid is actually present in the local cache.

Why

The previous check used desired.map(|d| cache.get(d)).is_some(), which returns true for every Some(kid) regardless of whether the cache contains that key. As a result, a missing or rotated JWK could never be fetched after startup, preventing key rotation from taking effect until the server restarted.

Fixes #78.

How

  • Replace the cache guard with desired.and_then(|d| cache.get(d)).is_some().
  • Add a regression test with a local JWKS endpoint that first serves old-kid, then serves new-kid, verifying that requesting the missing key triggers a refresh.

Testing

  • cargo +nightly fmt --all -- --check
  • cargo clippy -p lore-server --all-targets -- -D warnings --no-deps
  • cargo clippy --all-targets -- -D warnings --no-deps
  • cargo test

When a caller requests a missing desired key, the cache guard used Option::map and short-circuited every Some(kid) request before checking whether the key existed. This prevented key rotation from loading new JWKS entries until restart.

Use and_then so only an actual cached entry skips the fetch, and add a regression test that refreshes from a JWKS endpoint after the requested kid changes.

Fixes EpicGames#78

Signed-off-by: Sadovoi Grisha <gsad1030@gmail.com>

@mjansson mjansson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM - please be patient while we sort out the intake process and get this merged

@mjansson mjansson changed the title Fix JWK refresh cache check lore-server: Fix JWK refresh cache check Jul 8, 2026
@mjansson mjansson added the ready-to-import Approved by Epic staff for import into Lore label Jul 28, 2026
@epic-lore-bot epic-lore-bot Bot added imported Imported into Lore for internal review and removed ready-to-import Approved by Epic staff for import into Lore labels Jul 28, 2026
@epic-lore-bot

epic-lore-bot Bot commented Jul 28, 2026

Copy link
Copy Markdown

Imported as Lore CR-257.

epic-lore-bot Bot pushed a commit that referenced this pull request Jul 29, 2026
## What

Fix the JWK cache short-circuit so `fetch_new_keys(Some(kid))` skips the network fetch only when the requested `kid` is actually present in the local cache.

## Why

The previous check used `desired.map(|d| cache.get(d)).is_some()`, which returns `true` for every `Some(kid)` regardless of whether the cache contains that key. As a result, a missing or rotated JWK could never be fetched after startup, preventing key rotation from taking effect until the server restarted.

Fixes #78.

## How

- Replace the cache guard with `desired.and_then(|d| cache.get(d)).is_some()`.
- Add a regression test with a local JWKS endpoint that first serves `old-kid`, then serves `new-kid`, verifying that requesting the missing key triggers a refresh.

## Testing

- `cargo +nightly fmt --all -- --check`
- `cargo clippy -p lore-server --all-targets -- -D warnings --no-deps`
- `cargo clippy --all-targets -- -D warnings --no-deps`
- `cargo test`

```
Imported-PR: #99
Imported-From: 3d5f43b
Imported-Base: c920a7f
Imported-Merge: 937fbdd
Imported-Author: Sadovoi Grisha (e345ee)
Signed-off-by: Sadovoi Grisha <gsad1030@gmail.com>
GH-URL: #99
```

Lore-RevId: 372
Lore-Signature: 38f908424dad9d54c58d3741bedf78e02f0cd42da2499f1eca9227ed4daae847
TechArtistG pushed a commit to GenRobo/lore that referenced this pull request Aug 6, 2026
## What

Fix the JWK cache short-circuit so `fetch_new_keys(Some(kid))` skips the network fetch only when the requested `kid` is actually present in the local cache.

## Why

The previous check used `desired.map(|d| cache.get(d)).is_some()`, which returns `true` for every `Some(kid)` regardless of whether the cache contains that key. As a result, a missing or rotated JWK could never be fetched after startup, preventing key rotation from taking effect until the server restarted.

Fixes EpicGames#78.

## How

- Replace the cache guard with `desired.and_then(|d| cache.get(d)).is_some()`.
- Add a regression test with a local JWKS endpoint that first serves `old-kid`, then serves `new-kid`, verifying that requesting the missing key triggers a refresh.

## Testing

- `cargo +nightly fmt --all -- --check`
- `cargo clippy -p lore-server --all-targets -- -D warnings --no-deps`
- `cargo clippy --all-targets -- -D warnings --no-deps`
- `cargo test`

```
Imported-PR: EpicGames#99
Imported-From: 3d5f43b
Imported-Base: c920a7f
Imported-Merge: 937fbdd
Imported-Author: Sadovoi Grisha (e345ee)
Signed-off-by: Sadovoi Grisha <gsad1030@gmail.com>
GH-URL: EpicGames#99
```

Lore-RevId: 372
Lore-Signature: 38f908424dad9d54c58d3741bedf78e02f0cd42da2499f1eca9227ed4daae847
(cherry picked from commit 4fa9870)
TechArtistG pushed a commit to GenRobo/lore that referenced this pull request Aug 6, 2026
fetch_new_keys held cached_set's write guard for its whole body,
including the JWKS network round trip - and get_cached_key takes the
read side on every JWT verification, so one slow refresh stalled all
token verification server-wide; with no client timeout, a hung endpoint
held it indefinitely. Latent until the refresh short-circuit fix
(upstream PR EpicGames#99) made the request-path fetch reachable.

The refresh now runs under its own single-flight mutex: re-check the
cache under a read guard, fetch with a 10s request timeout holding no
cache lock, and take the write guard only to install the result.
Concurrent misses coalesce; verification reads never wait on I/O.

Regression test: a cached key stays readable (500ms bound) while a
refresh is wedged against an endpoint that never answers.

Not addressed here: repeated refreshes for kids that don't exist at the
endpoint (forged tokens) still cost one bounded fetch each; negative
caching is a follow-up.

GRID issue: GenRobo/GRID#1014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

imported Imported into Lore for internal review

Development

Successfully merging this pull request may close these issues.

JWK key-ring loader hardening

2 participants