Skip to content

fix(breg): name static-JWKS key rotation in refusals and document re-pinning - #984

Open
jeremi wants to merge 1 commit into
fix/oidc-typ-rfc9068-pairfrom
fix/breg-static-jwks-rotation
Open

fix(breg): name static-JWKS key rotation in refusals and document re-pinning#984
jeremi wants to merge 1 commit into
fix/oidc-typ-rfc9068-pairfrom
fix/breg-static-jwks-rotation

Conversation

@jeremi

@jeremi jeremi commented Sep 10, 2026

Copy link
Copy Markdown
Member

Stacks on #983 (shared files and import lines); retarget to main once that merges — GitHub does it automatically when the base branch is deleted.

What

A statically pinned jwksSource turns provider-side key rotation into a silent outage: every token fails with the same value-free authentication.refused, and nothing distinguished a rotated signing key from any other invalid credential (the story behind #982, seen with both ZITADEL and ThunderID pins; ThunderID regenerates keys on state loss).

The caller-facing refusal is deliberately unchanged. RegistryAuthenticator::authenticate now records a tracing::warn on the registry_breg::auth target when the platform verifier answers UnknownKid: the refused token cites a signing key outside the configured JWKS, and for a static pin that means the provider rotated — re-pin and restart. One log line separates rotation from an invalid token without leaking values.

The operations guide (operate/breg) gains the tested re-pin runbook next to the jwksSource: kind: static config: fetch the provider's current JWKS from its jwks_uri over TLS, confirm unannounced keys with the provider operator, replace the documentRef secret under the secret-file rules, restart breg, then verify a fresh login succeeds while tokens from removed keys stay refused. The runbook states the trust consideration: the new document becomes the anchor for bearer verification, so re-pinning is a deliberate act.

Verification

  • cargo test --locked -p registry-breg --features runtime --test http_auth — 26 passed. New static_jwks_rotation_is_diagnosed_and_repinning_restores_authentication drives the whole story with the MockIdP's rotated key (kid -1kid -2): pinned acceptance; rotation refusal asserting the captured log names the missing signing key; re-pin from the rotated document; stale-key refusal after re-pinning.
  • cargo clippy --locked -p registry-breg --all-targets --features runtime,tooling -- -D warnings clean; cargo fmt --check clean.

Closes #982.

…pinning

A statically pinned jwksSource turns provider-side key rotation into a
silent outage: every token fails with the same value-free
authentication.refused, and nothing on the engine surface distinguishes a
rotated signing key from any other invalid credential.

The refusal the caller sees stays unchanged; RegistryAuthenticator now
records on the registry_breg::auth target that the refused token cites a
signing key outside the configured JWKS and points at re-pinning, so an
operator can tell rotation from an invalid token in one log line. The
operations guide gains the matching runbook: fetch the provider's
current JWKS over TLS, replace the documentRef secret, restart, verify a
fresh login while removed keys stay refused.

The focused test drives the whole story with the MockIdP's second key:
pinned acceptance, rotation refusal with the named diagnostic, re-pin
from the rotated document, and stale-key refusal after re-pinning.

Closes #982.

Signed-off-by: Jeremi Joslin <jeremi@joslin.fr>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 10, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-10T18:37:28.769621Z e508ed8 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e508ed8fe0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// names an operational cause instead of a bad
// credential: a key outside the configured JWKS is how
// provider-side rotation presents.
if matches!(error, OidcError::UnknownKid) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not treat arbitrary unknown kids as key rotation

Any unauthenticated client can send a syntactically valid JWT with an allowed alg/typ and an arbitrary kid; TokenVerifier::verify_access_token then returns UnknownKid before verifying the signature, and enforce_kid_allowed returns the same variant for deliberately denied keys. This branch consequently emits a warning for every such request—even with discovery-backed JWKS—and falsely recommends re-pinning, allowing warning-log flooding and potentially sending operators toward an unnecessary trust-anchor change. Only diagnose static rotation when the source and cause are actually distinguishable, and rate-limit attacker-controlled misses.

Useful? React with 👍 / 👎.

use registry_platform_authcommon::{parse_bearer_token, validate_compact_access_token};
use registry_platform_oidc::{
is_access_token_typ_pair, Audience, JwksFetcher, TokenVerifier, TokenVerifierConfig,
is_access_token_typ_pair, Audience, JwksFetcher, OidcError, TokenVerifier, TokenVerifierConfig,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Add the required DCO sign-off

Commit 3b8485a25e5e9e39de874cb58c4cd46092da5064 has no Signed-off-by trailer, so it violates the repository's mandatory DCO rule; recreate this review commit with git commit -s before merging.

AGENTS.md reference: AGENTS.md:L370-L372

Useful? React with 👍 / 👎.


## Unreleased

- A Base Registry Engine deployment with a statically pinned `jwksSource`

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Split the docs and crate changes

This commit modifies both crates/registry-breg/ and docs/site/, despite the repository rule requiring each change to remain within one owning area. Split the documentation update from the runtime/test change so each review unit has a single owner.

AGENTS.md reference: AGENTS.md:L378-L380

Useful? React with 👍 / 👎.

Comment on lines +285 to +286
2. Replace the file the `documentRef` names with those bytes, keeping the secret-file rules
below (owner, mode, no trailing newline).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Cover environment-backed JWKS secrets in the runbook

When secretProviders.environment is enabled, the supported documentRef can be secret:env/<NAME>, so there is no file to replace and these recovery steps cannot be completed as written. Add an environment-backed branch that tells the operator how to replace the injected value and restart, rather than assuming every static pin uses the file provider.

AGENTS.md reference: docs/site/AGENTS.md:L116-L127

Useful? React with 👍 / 👎.

Comment on lines +27 to +29
at the re-pin procedure. The operations guide gained a tested re-pin
runbook: fetch the provider's current JWKS over TLS, replace the
`documentRef` secret, restart, and verify a fresh login while tokens from

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Do not call the runbook tested without coverage

The cited test replaces an in-memory JwksFetcher and calls authenticate; it does not fetch JWKS over TLS, replace a documentRef secret, restart breg, or perform a provider login. Calling the entire operational runbook “tested” therefore overstates the available evidence; either add coverage for those steps or weaken this claim.

AGENTS.md reference: docs/site/AGENTS.md:L40-L43

Useful? React with 👍 / 👎.

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.

1 participant