Skip to content

feat(local): support accept-invalid-certificate saved connections#53

Merged
BorisTyshkevich merged 3 commits into
mainfrom
feat/local-accept-invalid-cert
Jun 27, 2026
Merged

feat(local): support accept-invalid-certificate saved connections#53
BorisTyshkevich merged 3 commits into
mainfrom
feat/local-accept-invalid-cert

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

What

npm run local now supports clickhouse-client connections carrying
<accept-invalid-certificate>1</accept-invalid-certificate> (self-signed /
wrong-host TLS certs, common on dev tenants).

A browser can't bypass TLS validation from fetch(), so the SPA can't honour
the flag on its own. Instead the login picker flags such a connection and walks
the user through trusting the cert once, then connects normally.

Changes

  • build/local.py — read accept-invalid-certificate → emit insecure on
    basic & oauth host descriptors in config.json.
  • src/net/oauth-config.js — carry insecure through normalizeHost (it
    was being silently dropped, so the picker never saw the flag).
  • src/ui/login.js — insecure host → cert-trust panel (open-cluster link +
    guidance that the post-handshake redirect to an auth gateway/login page is
    expected). For oauth, the SSO redirect is held behind a Continue button so
    the cert is trusted before any post-login query hits the cluster.
  • build/local.py (port fix) — default to ClickHouse's HTTP-interface ports
    8443 (TLS) / 8123 (plain), not 443/80. Managed Altinity Cloud endpoints
    park an auth gateway on 443 (a browser GET 302s to acm.altinity.cloud/login),
    so 443 never reached ClickHouse. Mirrors the SPA's resolveTarget. An explicit
    <http_port> still overrides; a port already embedded in <hostname> is kept
    as-is.
  • Styles, README, docstring; tests for the picker flows + the insecure
    pass-through + the Continue double-submit guard.

Testing

  • npm test — 952 passing, per-file coverage gate green.
  • Verified end-to-end via python3 build/local.py against a real
    accept-invalid-certificate cluster (agent Chrome): cert panel renders, link
    opens :8443 (the direct HTTPS interface), Connect succeeds after the cert is
    trusted. Probing confirmed :443 is the ACM gateway (302 → login) and :8443
    is the real ClickHouse interface.

Review

Ran an internal review (medium); fixed the findings it surfaced: a pickOAuth
re-entrancy gap (Continue button double-submit racing the PKCE state), the
embedded-port double-append, and documented the http_port override.

🤖 Generated with Claude Code

https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN

BorisTyshkevich and others added 3 commits June 27, 2026 12:03
clickhouse-client connections carrying
<accept-invalid-certificate>1</accept-invalid-certificate> now surface a
cert-trust step in the login picker. A browser can't bypass TLS validation
from fetch(), so the SPA can't honour the flag on its own — instead it walks
the user through trusting the cert once.

- build/local.py: read accept-invalid-certificate; emit `insecure` on basic
  and oauth host descriptors in config.json.
- src/net/oauth-config.js: carry `insecure` through normalizeHost (it was
  silently dropped — the picker never saw the flag).
- src/ui/login.js: insecure host → show a cert-trust panel (open-cluster link
  + guidance that the post-handshake redirect to an auth gateway/login page is
  expected and unrelated). For oauth, hold the SSO redirect behind a Continue
  button so the cert is trusted before any post-login query hits the cluster.
- styles.css: panel styling. README + docstring: document the flow.
- tests: login picker (basic/oauth/clear) + oauth-config insecure pass-through.

Verified end-to-end via `python3 build/local.py` against the `audit` support
connection (support-a.tenant-a.dev.altinity.cloud): the cluster root
302-redirects to acm.altinity.cloud/login after the TLS handshake, which is
the "wrong redirect" the panel now explains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
A saved connection without an explicit http_port built a portless URL, so
secure hosts resolved to :443. On managed Altinity Cloud endpoints :443 is an
auth gateway (a browser GET 302s to acm.altinity.cloud/login) — the SPA never
reached ClickHouse, and the :443 cert is valid so "accept the certificate"
trusted nothing relevant. The real HTTPS interface (with the self-signed cert)
is :8443.

Default secure→8443, plain→8123 (ClickHouse's own HTTP-interface defaults),
mirroring the SPA's resolveTarget for a bare host. Verified all configured
clusters answer /ping on 8443; support-a only works there.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
- login.js: pickOAuth now guards `if (busy) return` (it became reachable from
  the cert panel's Continue button, not just the picker's onchange, so a fast
  double-click could race two OAuth flows and corrupt the PKCE verifier/state).
  The Continue button also disables itself on click, mirroring doSso. (#1)
- build/local.py: don't append a default port when <hostname> already carries
  one (host:port → was becoming host:port:8443). (#2)
- build/local.py: document that an explicit <http_port> overrides the 8443/8123
  default (e.g. 443 for a proxy that fronts the HTTP interface with no gateway). (#3)
- test: cover the Continue double-submit guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MUc6UU9qs5J3u7qoy5YoNN
@BorisTyshkevich BorisTyshkevich merged commit 2843cdd into main Jun 27, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant