fix: fetch the OIDC discovery document lazily instead of at import time - #133
Open
bladewing wants to merge 1 commit into
Open
fix: fetch the OIDC discovery document lazily instead of at import time#133bladewing wants to merge 1 commit into
bladewing wants to merge 1 commit into
Conversation
bladewing
force-pushed
the
fix/lazy-well-known-discovery
branch
from
August 26, 2026 07:58
c2b7dca to
ba86698
Compare
`oidc/constants.py` performed a `requests.get()` against `<OIDC_DOMAIN>/.well-known/openid-configuration` with a 2 second timeout while the module was being imported. Every Sentry process imports the provider through `apps.py`, including the ~20 consumer and worker containers of a self-hosted installation that never perform a login. Whenever the identity provider took longer than 2 seconds to answer, all of those processes died with `requests.exceptions.ReadTimeout` during import and were restarted immediately by Docker, which in turn put more load on the host (and on the identity provider if it runs on the same machine). On one installation this resulted in ~189,000 restarts per consumer and a load average of ~1,300 that made the host unreachable. The discovery document is now fetched on first use (when a login pipeline is built or the configure view is rendered), cached for the lifetime of the process, and a failed fetch is logged as a warning and falls back to the statically configured `OIDC_*_ENDPOINT` settings instead of raising. The timeout was raised to 15 seconds and both the timeout and the back-off after a failure are configurable through `OIDC_WELL_KNOWN_TIMEOUT` and `OIDC_WELL_KNOWN_RETRY_AFTER`. `PROVIDER_NAME` (used as the class-level `OIDCProvider.name`) no longer falls back to the *discovered* issuer, because a class attribute cannot depend on network I/O; it still honours `OIDC_PROVIDER_NAME` and `OIDC_ISSUER`. The configure view keeps the discovered issuer as a fallback via `get_provider_name()`.
bladewing
force-pushed
the
fix/lazy-well-known-discovery
branch
from
August 27, 2026 08:29
ba86698 to
767350e
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
oidc/constants.pyperforms arequests.get()against<OIDC_DOMAIN>/.well-known/openid-configurationwithtimeout=2.0while the module is being imported. Since the provider is registered fromapps.py, every Sentry process imports it — including the ~20 consumer/worker containers of a self-hosted installation that never perform a login.Whenever the identity provider takes longer than 2 s to answer, every one of those processes dies with
and is restarted immediately by Docker (
restart: unless-stopped). Twenty Python processes booting at once drive up the load, the IdP gets slower, and the loop feeds itself. On our installation this ran for months at ~189,000 restarts per consumer and yesterday reached a load average of ~1,300 that made the host unreachable (sshd and the reverse proxy stopped answering).Fix
OIDCLogin,OAuth2Callback,get_refresh_token_url,get_user_info) or the configure view is rendered — viaget_well_known(), and cached for the lifetime of the process.{}; the accessors fall back to the statically configuredOIDC_*_ENDPOINTsettings instead of raising. After a failure the provider is not contacted again forOIDC_WELL_KNOWN_RETRY_AFTERseconds (default 30) so a slow IdP cannot stall every request.OIDC_WELL_KNOWN_TIMEOUT.AUTHORIZATION_ENDPOINT,TOKEN_ENDPOINT,USERINFO_ENDPOINT,ISSUERare kept (now purely settings-derived) so existing imports keep working; new accessorsget_authorization_endpoint(),get_token_endpoint(),get_userinfo_endpoint(),get_issuer(),get_provider_name()return the discovered values.Behaviour change to be aware of
PROVIDER_NAME(used as the class attributeOIDCProvider.name) no longer falls back to the discoveredissuer, because a class attribute is evaluated at import time and must not depend on network I/O. It still honoursOIDC_PROVIDER_NAME, thenOIDC_ISSUER, then"OIDC". The configure view keeps the discovered issuer as a fallback throughget_provider_name(). README updated accordingly. If you would rather keep the old fallback for the class attribute, I'm happy to discuss options (e.g. a metaclass property), but I think a static name is the safer default.Testing
tests/test_discovery_unit.py(uses the existing Sentry stubs fromtest_provider_unit.py): import performs no request; first pipeline build fetches once and caches; aReadTimeoutdoes not raise and falls back to static settings, with the back-off honoured; configuredOIDC_ISSUERtakes precedence; withoutOIDC_DOMAINnothing is fetched.ruff check/ruff format --checkpass.authorization_endpoint.