hermes: derive the oauth2-proxy callback from the request host
Restoring the legacy chat/triage hosts is not enough on its own: both proxies pinned --redirect-url to the renamed host, and oauth2-proxy returns that value verbatim whenever it carries a host (getOAuthRedirectURI short-circuits on redirectURL.Host != ""). A login started on a legacy host would therefore send the browser to the canonical host's callback, while the CSRF cookie stays behind: it is issued with the __Host- prefix, which forbids a Domain attribute and pins it to the exact origin. The callback lands without it and fails as "unable to find a valid CSRF token". Drop the host from both callback URLs so oauth2-proxy builds them from the request host instead. For the renamed hosts the derived value is byte-identical to the pinned one, so their behaviour is unchanged; the legacy hosts now complete a login on the host the user actually visited. Derivation reads X-Forwarded-Host only behind a trusted reverse proxy, which both deployments already declare, and Keycloak still matches the result against the redirect URIs registered by the ensure script. The agent proxy keeps its pinned callback: it serves one host. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
fd4bf69007
commit
b2cca91b6b
@ -82,7 +82,12 @@ spec:
|
|||||||
args:
|
args:
|
||||||
- --provider=oidc
|
- --provider=oidc
|
||||||
- --config=/vault/secrets/oidc-config
|
- --config=/vault/secrets/oidc-config
|
||||||
- --redirect-url=https://triage.bstein.dev/oauth2/callback
|
# Host-less on purpose: oauth2-proxy then builds the callback from
|
||||||
|
# the (trusted-proxy) request host, so a login started on the legacy
|
||||||
|
# host completes there. Pinning a host would set the __Host- CSRF
|
||||||
|
# cookie on one origin and land the callback on the other, which
|
||||||
|
# fails as "unable to find a valid CSRF token".
|
||||||
|
- --redirect-url=/oauth2/callback
|
||||||
- --oidc-issuer-url=https://sso.bstein.dev/realms/atlas
|
- --oidc-issuer-url=https://sso.bstein.dev/realms/atlas
|
||||||
- --user-id-claim=sub
|
- --user-id-claim=sub
|
||||||
- --code-challenge-method=S256
|
- --code-challenge-method=S256
|
||||||
@ -181,7 +186,8 @@ spec:
|
|||||||
args:
|
args:
|
||||||
- --provider=oidc
|
- --provider=oidc
|
||||||
- --config=/vault/secrets/oidc-config
|
- --config=/vault/secrets/oidc-config
|
||||||
- --redirect-url=https://chat.bstein.dev/oauth2/callback
|
# Host-less on purpose; see the triage proxy above.
|
||||||
|
- --redirect-url=/oauth2/callback
|
||||||
- --oidc-issuer-url=https://sso.bstein.dev/realms/atlas
|
- --oidc-issuer-url=https://sso.bstein.dev/realms/atlas
|
||||||
- --user-id-claim=sub
|
- --user-id-claim=sub
|
||||||
- --code-challenge-method=S256
|
- --code-challenge-method=S256
|
||||||
|
|||||||
@ -133,6 +133,29 @@ def test_ensure_script_registers_legacy_and_renamed_origins_together(
|
|||||||
assert f"https://{legacy}" in call.group(0)
|
assert f"https://{legacy}" in call.group(0)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"deployment", ["oauth2-proxy-hermes-chat", "oauth2-proxy-hermes-triage"]
|
||||||
|
)
|
||||||
|
def test_multi_host_proxies_derive_their_callback_from_the_request(deployment: str):
|
||||||
|
"""A pinned callback host breaks logins started on the other hostname.
|
||||||
|
|
||||||
|
oauth2-proxy returns ``--redirect-url`` verbatim when it carries a host, so
|
||||||
|
a login started on the legacy host would send the browser to the canonical
|
||||||
|
host's callback. The CSRF cookie uses the ``__Host-`` prefix and cannot
|
||||||
|
cross origins, so the callback fails to find it. Leaving the URL host-less
|
||||||
|
makes oauth2-proxy build it from the request host instead.
|
||||||
|
"""
|
||||||
|
spec = _named(HERMES / "oauth2-proxy.yaml", "Deployment", deployment)
|
||||||
|
args = spec["spec"]["template"]["spec"]["containers"][0]["args"]
|
||||||
|
redirect = next(a for a in args if a.startswith("--redirect-url="))
|
||||||
|
value = redirect.split("=", 1)[1]
|
||||||
|
assert value.startswith("/"), f"{deployment} pins a callback host: {value}"
|
||||||
|
|
||||||
|
# Derivation only trusts X-Forwarded-Host behind a declared reverse proxy.
|
||||||
|
assert "--reverse-proxy=true" in args
|
||||||
|
assert any(a.startswith("--trusted-proxy-ip=") for a in args)
|
||||||
|
|
||||||
|
|
||||||
def test_ensure_job_is_rerun_whenever_the_script_changes():
|
def test_ensure_job_is_rerun_whenever_the_script_changes():
|
||||||
"""The Job is immutable, so a stale name silently skips the rerun."""
|
"""The Job is immutable, so a stale name silently skips the rerun."""
|
||||||
job = _named(
|
job = _named(
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user