diff --git a/services/hermes/oauth2-proxy.yaml b/services/hermes/oauth2-proxy.yaml index 5cd9d01f..c32d0c88 100644 --- a/services/hermes/oauth2-proxy.yaml +++ b/services/hermes/oauth2-proxy.yaml @@ -82,7 +82,12 @@ spec: args: - --provider=oidc - --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 - --user-id-claim=sub - --code-challenge-method=S256 @@ -181,7 +186,8 @@ spec: args: - --provider=oidc - --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 - --user-id-claim=sub - --code-challenge-method=S256 diff --git a/testing/tests/test_hermes_public_host_continuity.py b/testing/tests/test_hermes_public_host_continuity.py index eb62c696..ff66629f 100644 --- a/testing/tests/test_hermes_public_host_continuity.py +++ b/testing/tests/test_hermes_public_host_continuity.py @@ -133,6 +133,29 @@ def test_ensure_script_registers_legacy_and_renamed_origins_together( 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(): """The Job is immutable, so a stale name silently skips the rerun.""" job = _named(