Skip to content

feat(ssh): full tunnel parity with dockit — chains, profiles, proxies, TOFU - #174

Merged
Blankll merged 9 commits into
masterfrom
feat/ssh-tunnel-full-parity
Oct 9, 2026
Merged

Blankll merged 9 commits into
masterfrom
feat/ssh-tunnel-full-parity

Conversation

@Blankll

@Blankll Blankll commented Oct 8, 2026

Copy link
Copy Markdown
Member

Summary

Lands the full SSH-tunnel capability alignment from the cross-repo audit: SqlKit now matches DocKit's implementation surface and closes the production-readiness gaps found on both sides.

Capabilities

Capability Detail
Deterministic tunnel keys Same hops + same target reuse one tunnel across connections (tunnel_key() — dockit's strategy)
Multi-hop chains start_chain — hop N connects to hop N+1, last hop forwards to the database; system proxy applies to the first hop only
SSH profiles Stored in .store.dat, CRUD commands, management dialog, Ultimate-gated management on the connections page
~/.ssh/config import OpenSSH-subset parser (Host/HostName/Port/User/IdentityFile, wildcards skipped) with the full ported test suite
System proxy detect_system_proxy (hyper-util Matcher + macOS SCDynamicStore exceptions) + use_system_proxy HTTP-CONNECT on the first hop
SOCKS5 / HTTP CONNECT servers ssh -D equivalent; expose_lan selects SOCKS5 vs PortForward; dual-protocol server dispatches on first byte
Host-key TOFU (SqlKit-first) check_server_key verifies against a pinned-fingerprint store — first sight pins, mismatches reject with a recovery message

Known limitations (documented in docs/ssh-tunnel-architecture.md)

  • verify_host_key still defaults to lenient; TOFU engages when enabled
  • TLS-over-tunnel SNI mismatch for domain-issued certs (dockit #472 class) — workaround: ssl = disable inside trusted tunnels
  • Credentials at rest remain in the connection store (keychain integration planned; dockit shares this)

Test plan

  • cargo test --lib — 502 passed (94 ssh: ported parser suite, known_hosts TOFU, chain/transport)
  • jest — 611 passed
  • npm run lint:check + vue-tsc --noEmit clean

…, TOFU

Port the remaining SSH tunnel capabilities from dockit and close the
production-readiness gaps found in the audit:

- deterministic tunnel keys: same SSH hops + same target reuse one
  tunnel across connections (no duplicate tunnels per bastion)
- multi-hop chains: transport.rs now builds hop chains via
  TunnelManager.start_chain — hop N connects to hop N+1, the last hop
  forwards to the database
- SSH profiles: stored in .store.dat ("sshProfiles") with CRUD commands
  (list/save/delete) and a profile management dialog; connection dialogs
  accept ordered profile hops as the tunnel source
- ~/.ssh/config import: full OpenSSH-subset parser (Host/HostName/Port/
  User/IdentityFile, wildcards skipped) + list command + UI import
- system proxy: detect_system_proxy command (hyper-util Matcher +
  macOS SCDynamicStore exceptions list) and use_system_proxy on hop
  configs — first hop connects through the OS proxy via HTTP CONNECT
- SOCKS5 / HTTP CONNECT local servers (ssh -D equivalent) from dockit:
  expose_lan selects the mode, the dual-protocol server dispatches on
  the first byte
- host-key TOFU verification (SqlKit-first): check_server_key verifies
  against a pinned-fingerprint store; first sight pins, mismatches
  reject with a recovery message; verify_host_key still defaults to
  lenient

New deps: hyper-util, system-configuration, fast-socks5 (1.0),
percent-encoding. Also: eslint rule antfu/top-level-function disabled
(AGENTS.md prefers const-arrow), architecture doc added
(docs/ssh-tunnel-architecture.md).

Tests: cargo 502 lib tests (94 ssh incl. ported parser suite + new
known_hosts TOFU suite); jest 611; lint + type-check clean.
…leanup

Review of #174 surfaced three real gaps:

- profile round-trip was lossy: save expanded profileIds into
  transport_layers, and load reconstructed sshTunnel from those layers —
  profile identity was dropped, so one edit cycle silently degraded an
  SSH-profile connection back to inline. ServerConfig now persists the
  verbatim sshTunnel object and load prefers it (transport layers stay
  as the execution form)
- buildTransportLayers expanded profile hops from a profile store that
  may not have been fetched yet (fresh session → save → silently empty
  layers); it is now async and fetches on demand
- the TOFU mismatch message told users to "remove the saved host key"
  with no way to do it — new unpin_ssh_host command restores the
  recovery path

Also cleans port warnings (unused param, irrefutable let, dead helper).
…kfile

- unpin_ssh_host command: the TOFU mismatch message told users to remove
  the saved host key — now there is a recovery path (register included)
- ServerConfig persists the frontend's sshTunnel object verbatim
  (sshTunnel passthrough) — the save/load round-trip no longer loses
  profile hops (transport layers stay as the execution form)
- npm audit fix: newly disclosed dev-chain advisories (handlebars,
  js-yaml via jest tooling) broke the audit gate repo-wide
- connectionStore test: connect awaits the async transport-layer build
  before reaching the mocked API — flush microtasks instead of racing it
The tests raced on a process-wide OnceLock store path — only the first
test's directory won, and parallel scheduling differences (ubuntu vs
macos) made later tests see a foreign store and fail. The core now
takes the store path as a parameter; tests use isolated files.
…l/oracle)

Mirrors DocKit's event-search test stack: an SSH bastion (password +
three test keys: ED25519, RSA PEM, ED25519-with-passphrase) with four
databases on the internal network only — PostgreSQL 17, MySQL 8.4,
SQL Server 2022 and Oracle 23ai Free. Nothing but the bastion's 2223 is
exposed to the host (2223 keeps DocKit's 2222 stack runnable alongside).

Private test keys are gitignored; authorized_keys and public keys are
committed. README documents the stack and the SqlKit connection settings
for each database.
CI lint gate fails on yaml/quotes, yaml/block-sequence and
yaml/plain-scalar rules; autofix + drop a stray blank line. Parsed before
and after with a YAML loader to confirm the compose document is
semantically unchanged.
@Blankll
Blankll merged commit c29474c into master Oct 9, 2026
3 checks passed
Blankll added a commit that referenced this pull request Oct 9, 2026
…leanup

Review of #174 surfaced three real gaps:

- profile round-trip was lossy: save expanded profileIds into
  transport_layers, and load reconstructed sshTunnel from those layers —
  profile identity was dropped, so one edit cycle silently degraded an
  SSH-profile connection back to inline. ServerConfig now persists the
  verbatim sshTunnel object and load prefers it (transport layers stay
  as the execution form)
- buildTransportLayers expanded profile hops from a profile store that
  may not have been fetched yet (fresh session → save → silently empty
  layers); it is now async and fetches on demand
- the TOFU mismatch message told users to "remove the saved host key"
  with no way to do it — new unpin_ssh_host command restores the
  recovery path

Also cleans port warnings (unused param, irrefutable let, dead helper).
@Blankll
Blankll deleted the feat/ssh-tunnel-full-parity branch October 9, 2026 09:53
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