Commit Graph
2 Commits
Author SHA1 Message Date
overseer 80ac786a54 Admin auth: harden JWKS cache against outage amplification (P2) and corrupt/unknown-key handling (P3)
- P2: verify_admin_token parses and enforces the JWT header (alg ==
  RS256, kid present) BEFORE consulting the JWKS cache, so a
  structurally invalid token costs no fetch, no lock, no provider load.
- P2: JwksCache gains a short negative-cache backoff window (5s,
  time.monotonic-recorded) after a failed fetch: within it, get()
  re-raises the recorded fault without touching the fetcher — and
  without holding or queueing on the lock — so an outage costs one
  fetch per window instead of one per request. A successful fetch
  clears the backoff; nothing is cached indefinitely (TTL unchanged).
- P3: _matching_key widens the from_jwk handling to
  (PyJWTError, binascii.Error, ValueError, TypeError, KeyError) so a
  JWKS entry matching the kid but carrying corrupt n/e maps to the
  SAME uniform 401 instead of surfacing a 500.
- P3: an unmatched kid now forces ONE TTL-bypassing refetch and
  retries the match once, so rotated keys verify without waiting out
  the TTL; the forced refetch is itself bounded (inside the backoff
  window or the 5s kid-refetch interval the cached document is served
  as-is), so unknown-kid floods cannot force a fetch per request.
- Tests: no-fetch proofs for garbage/non-RS256 tokens, corrupt-entry
  401, outage fail-fast (sequential + concurrent burst), backoff
  lapse/recovery, outage-as-server-fault, and the unknown-kid
  refetch: one forced, throttled, resume-after-interval, rotation
  picked up, failed forced refetch propagates then fails fast.
  Existing tests unchanged (uniform 401 contract intact).
2026-09-23 00:30:04 -04:00
overseer 3cdedb37c8 Admin plane: validate Authentik OIDC JWTs on /api/v1/admin/*
The admin plane had no authentication: any caller could reach its
routes once they existed. This adds the plane's ONE reusable FastAPI
dependency (require_admin_jwt in api/auth.py) that verifies an
Authentik OIDC JWT — Bearer extraction, RS256-only signature check
against the provider's JWKS, issuer, audience, required exp, and
enforced nbf — and raises the SAME 401 for every failure so the
response never leaks which check failed.

The JWKS is fetched over httpx once per app (JwksCache, 5-minute TTL,
asyncio-lock-guarded, failed fetches never cached) and tests stub the
fetcher, so the suite proves signature/expiry/audience enforcement
with locally generated RSA key pairs and zero network access.

ApiConfig grows the three required OIDC settings
(NESTQUEST_OIDC_ISSUER/AUDIENCE/JWKS_URL) under the existing fail-fast
discipline: an unconfigured issuer must not silently leave the admin
plane open, so from_env raises rather than inventing a default.

This is authentication only — the verified claims are the dependency's
return value, so the nestquest-admins group check is the next task's
addition, not a rewrite. PyJWT[crypto] joins the dev dependency group
(the API deployable's runtime manifest lands in Feature 17).

Maestro task: e70e6bfb-085a-4781-a3f4-276355bbbd19
2026-09-22 23:55:02 -04:00