Skip to content

Antalya 26.3: Fixes for OAuth --login flow#2086

Merged
zvonand merged 2 commits into
antalya-26.3from
fix/antalya-26.3/oauth-login-flow-fix
Jul 21, 2026
Merged

Antalya 26.3: Fixes for OAuth --login flow#2086
zvonand merged 2 commits into
antalya-26.3from
fix/antalya-26.3/oauth-login-flow-fix

Conversation

@zvonand

@zvonand zvonand commented Jul 20, 2026

Copy link
Copy Markdown
Member

Closes #1691
Closes #1822

Changelog category (leave one):

  • Bug Fix (user-visible misbehavior in an official stable release)

Changelog entry (a user-readable short description of the changes that goes to CHANGELOG.md):

Fix OAuth2 login CSRF-state lea, unbounded OIDC response in clickhouse-client and confidential clients auth

CI/CD Options

Exclude tests:

  • Fast test
  • Integration Tests
  • Stateless tests
  • Stateful tests
  • Performance tests
  • Aarch64 tests
  • All with ASAN
  • All with TSAN
  • All with MSAN
  • All with UBSAN
  • All with Coverage
  • All Regression
  • Disable CI Cache

Regression jobs to run:

  • Fast suites (mostly <1h)
  • Aggregate Functions (2h)
  • Alter (1.5h)
  • Benchmark (30m)
  • ClickHouse Keeper (1h)
  • Iceberg (2h)
  • LDAP (1h)
  • OAuth (5m)
  • Parquet (1.5h)
  • RBAC (1.5h)
  • SSL Server (1h)
  • S3 (2h)
  • S3 Export (2h)
  • Swarms (30m)
  • Tiered Storage (2h)

@zvonand zvonand added port-antalya PRs to be ported to all new Antalya releases antalya-26.3 labels Jul 20, 2026
@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

Workflow [PR], commit [f3f8754]

@zvonand zvonand changed the title Antalya 26.3: Fix OAuth2 login CSRF-state leak, fix unbounded OIDC response Antalya 26.3: Fixes for OAuth --login flow Jul 20, 2026
@zvonand

zvonand commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

@blau-ai

@blau-ai

blau-ai commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

CI triage — @zvonand

Verdict: 0 of the failures are caused by this PR. All red checks are either infra (Grype), pre-existing/flaky server-side regression tests, or aggregate gates that just mirror those. The parts of CI that actually exercise this diff are green: all Build (*) variants, Fast test (0 failed / 7993 passed), stateless tests, and integration tests all pass — so the client code compiles and links on every configuration.

This PR only touches src/Client/* (OAuth --login flow, entirely behind #if USE_JWT_CPP && USE_SSL and only reachable via clickhouse-client --login). None of the failing checks exercise that path.

Check Class PR-caused?
Grype Scan (server / keeper / -alpine) infra ❌ no
RegressionTestsRelease / Iceberg (2) / iceberg_2 pre-existing / server-side ❌ no
SQLLogic test pre-existing / server-side ❌ no
FinishCIReport, PR aggregate gate ❌ no (mirrors the above)

Details

Grype Scan (all 3 images) — infra, not a real vulnerability finding. The status line even reads "Completed with 0 high/critical vulnerabilities". The step failed because Grype could not load its vulnerability database:

WARN  error updating db ... failed to hydrate database: failed to open db:
      unable to migrate: constraint failed: UNIQUE constraint failed:
      operating_systems.name, ... (2067)
ERROR failed to load vulnerability db: database does not exist
##[error]Process completed with exit code 1.

The scan never ran → count is 0 → the status script marks the commit status failure regardless. This is a Grype-DB/download problem on the runner, independent of any source change. Next step: just re-run the Grype jobs (GrypeScanServer, GrypeScanKeeper, GrypeScanServer (-alpine)); they typically go green on retry once the DB downloads cleanly.

RegressionTestsRelease / Iceberg (2) / iceberg_2 — server-side Iceberg tests, cannot be affected by a client-only diff. All 13 failing features are under /iceberg/export partition/**:

✘ [ Fail ] '/iceberg/export partition/ice catalog/transactions/commit survives pre-publish failure (non-retry cleanup)'
✘ [ Fail ] '/iceberg/export partition/ice catalog/casting/lossy/UInt64 narrows to Int32 without allow_lossy_cast'
✘ [ Fail ] '/iceberg/export partition/glue catalog/transactions'
... (110 features: 84 ok, 13 failed, 13 skipped)

export partition is an area under active development on antalya-26.3 (cf. the recent export-partition-retry-backoff merge, #1984). These are pre-existing/flaky regression failures on the target branch, unrelated to OAuth. Next step: confirm against a base-branch regression run and/or re-run; no action needed in this PR.

SQLLogic test — server-side SQL-engine conformance tests run against the server binary; they don't use clickhouse-client --login, so this diff can't change their outcome. (The per-scenario log for this run has already expired on the runner, but the classification holds from what the check exercises.) Next step: safe to re-run.

FinishCIReport / PR — aggregate gates. They are red only because the checks above are red; they carry no independent failure.

Bottom line

Nothing to fix in the PR for CI. I'd re-run the three Grype jobs and the Iceberg regression job; if the Iceberg failures persist, they should be tracked against antalya-26.3 rather than this PR. Happy to open a fix branch if any of these turns out to be real on a re-run — just ping me.

(Analysis is evidence-first from CI logs; I can't build/run ClickHouse in this container.)

@zvonand

zvonand commented Jul 21, 2026

Copy link
Copy Markdown
Member Author

@blau-ai does this PR actually close the issues that it is intended to close?

@blau-ai

blau-ai commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

@zvonand Yes — I traced each linked issue against the diff, and the PR closes both. Here's the mapping.

#1822 — confidential-client device flow

Root cause: --login=device never put client_secret on the device-authorization request, so a confidential IdP client rejected the very first call with invalid_client.

Fix (correct): the device-authorization body is now built by buildDeviceAuthorizationRequestBody() (OAuthFlowRunner.cpp), which appends client_secret when configured and omits it entirely for public clients (no empty value):

if (!creds.client_secret.empty())
    body += "&client_secret=" + urlEncodeOAuth(creds.client_secret);

The two later device-flow steps (the polling token request) and the auth-code token exchange already sent the secret before this PR, so this was the one missing spot — matching the issue exactly. ✅

#1691 — two Medium defects from the #1606 audit

Defect 1 — /start leaks CSRF state to any local process: fixed via the issue's preferred direction. The /start redirect endpoint is removed entirely; the browser is now launched directly with the auth URL, and the loopback server serves only /callback. Callback acceptance is now gated in the handler itself (received_state == expected_state), so a blind local process can't pre-empt the flow with a forged callback, and a mismatch is recorded so a timeout reports a likely CSRF failure instead of a bare timeout. ✅

One honest caveat the PR documents itself: launching the browser directly moves the URL (carrying state + PKCE code_challenge) into the helper's argv (/proc/<pid>/cmdline). That's a different local-disclosure vector than the one #1691 flagged (the loopback /start), and it's the standard native-app trade-off (gcloud/aws sso do the same). The specific /start leak in the issue is closed; the residual argv exposure is called out in the code comment and partially mitigated by the state gate.

Defect 2 — unbounded OIDC discovery read (memory-exhaustion DoS): fixed. fetchDeviceEndpointFromIssuer (OAuthProviderPolicy.cpp) no longer uses Poco::StreamCopier::copyToString; it uses the bounded copyStreamWithLimit(stream, body, OAUTH_MAX_RESPONSE_BYTES) (1 MiB cap, AUTHENTICATION_FAILED on overflow). The helper + constants were centralized into OAuthFlowRunner.h so the browser, device, and discovery paths share one limit — which is the "consider centralizing" suggestion in the issue. ✅

Regression tests

src/Client/tests/gtest_oauth_login.cpp gains coverage for all three:

Verdict

The diff addresses all three defects described in the two issues, with matching regression tests, so the Closes #1691 / Closes #1822 are accurate. I did not build/run locally (not possible in this environment) — final validation is on CI.

On CI: the currently-red checks (Grype scans reporting 0 high/critical, Iceberg (2) regression, SQLLogic, FinishCIReport) are unrelated to this client-side OAuth C++ change; none is an OAuth test. If you want, I can dig into whether those are pre-existing/flaky on antalya-26.3 — just say the word.

@zvonand
zvonand merged commit 60d37ba into antalya-26.3 Jul 21, 2026
248 of 259 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

antalya-26.3 port-antalya PRs to be ported to all new Antalya releases

Projects

None yet

2 participants