mirror of
https://github.com/turnstonelabs/turnstone.git
synced 2026-08-12 23:12:23 -06:00
fix(oidc/console): unblock obo edits when OIDC off; latch config-invalid rediscovery
Round-8 review follow-up — two correctness follow-ons from the round-7 rediscovery/console-gate fixes, plus two cleanups. - The console obo write gate ran the OIDC-deployment checks on EVERY update, so once OIDC was operator-disabled any edit of an existing oauth_obo server — including the natural remedy of setting enabled=false — was rejected 400, leaving DELETE as the only way out. The deployment-level checks (encryption key, OIDC enabled/configured, capture opt-in, valid grant profile) now run only when a write is a NEW obo enablement (create or flip INTO obo); a same-type edit keeps only the per-server validity checks (audience required, entra-scope reject), so an operator can always disable or edit an existing obo server. - Probing rediscovery with enabled forced True carried the retryable boot flag into discover_oidc, whose config-error branches returned enabled= False without clearing it, so a config-invalid IdP (an endpoint failing SSRF/same-origin validation) re-probed every 60s forever. The config- error branches now latch discovery_retryable=False (terminal), and maybe_rediscover installs that terminal config so the node stops probing; the transient fetch/degraded branches keep retrying. Cleanups: fold the obo missing-expires_in fallback into _expires_at_from_response via a default_ttl_seconds param (one owner of the stored-expiry format), and drop the redundant audience-change inequality already guaranteed by the no-op normalization (matching the sibling scopes_changing).
This commit is contained in:
@@ -2560,6 +2560,54 @@ class TestRuntimeRediscovery:
|
||||
# The healed config no longer advertises a retryable failure.
|
||||
assert state.oidc_config.discovery_retryable is False
|
||||
|
||||
def test_rediscover_latches_terminal_on_config_error_and_stops_probing(self):
|
||||
"""Review finding: probing with enabled forced True carries the
|
||||
retryable boot flag into discover_oidc, whose config-error branches must
|
||||
latch discovery_retryable=False (terminal) — and maybe_rediscover must
|
||||
INSTALL that terminal config — or a config-invalid IdP (endpoint failing
|
||||
SSRF/same-origin) re-probes every cooldown window forever. Drives the
|
||||
real discover_oidc: the discovered token_endpoint is on a foreign host,
|
||||
so validation rejects it as a config error."""
|
||||
from turnstone.core.oidc import maybe_rediscover_oidc
|
||||
|
||||
state = self._disabled_retryable_state() # issuer=https://idp.example.com
|
||||
bad_doc = {
|
||||
"authorization_endpoint": "https://idp.example.com/authorize",
|
||||
"token_endpoint": "https://attacker.example/token", # foreign host
|
||||
"userinfo_endpoint": "https://idp.example.com/userinfo",
|
||||
"jwks_uri": "https://idp.example.com/.well-known/jwks.json",
|
||||
}
|
||||
mock_response = MagicMock()
|
||||
mock_response.json.return_value = bad_doc
|
||||
mock_response.raise_for_status = MagicMock()
|
||||
|
||||
probes = {"n": 0}
|
||||
|
||||
async def _get(url):
|
||||
probes["n"] += 1
|
||||
return mock_response
|
||||
|
||||
async def _run():
|
||||
client = _mock_async_client(_get)
|
||||
with (
|
||||
patch("socket.getaddrinfo", return_value=[(2, 1, 6, "", ("93.184.216.34", 0))]),
|
||||
patch("httpx.AsyncClient", return_value=client),
|
||||
):
|
||||
await maybe_rediscover_oidc(state)
|
||||
# Same window: cooldown already gates a second probe.
|
||||
await maybe_rediscover_oidc(state)
|
||||
# Force the cooldown open — but the config is now terminal, so
|
||||
# the retryable guard should short-circuit before any probe.
|
||||
state.oidc_rediscover_last = None
|
||||
await maybe_rediscover_oidc(state)
|
||||
|
||||
asyncio.run(_run())
|
||||
|
||||
# Still disabled, but LATCHED terminal (not retryable) — one probe only.
|
||||
assert state.oidc_config.enabled is False
|
||||
assert state.oidc_config.discovery_retryable is False
|
||||
assert probes["n"] == 1
|
||||
|
||||
def test_rediscover_cooldown_gates_repeat_probes(self):
|
||||
from turnstone.core.oidc import maybe_rediscover_oidc
|
||||
|
||||
|
||||
Reference in New Issue
Block a user