Skip to content

fix(ssh): resolve ~/.ssh/config aliases at connect time (#977)#979

Merged
datlechin merged 5 commits intomainfrom
fix/ssh-config-runtime-resolution-977
May 4, 2026
Merged

fix(ssh): resolve ~/.ssh/config aliases at connect time (#977)#979
datlechin merged 5 commits intomainfrom
fix/ssh-config-runtime-resolution-977

Conversation

@datlechin
Copy link
Copy Markdown
Member

Summary

Fixes #977 — typing an SSH config alias like aia-bastion in the SSH host field used to fail with EAI_NONAME because the alias was passed straight to getaddrinfo instead of being resolved through ~/.ssh/config. This PR makes the SSH config a runtime source of truth for tunnel creation, with full ssh_config(5)-compatible matching.

  • Aliases now resolve at connection time, matching ssh(1) behavior. Editing ~/.ssh/config takes effect on the next connection (mtime-cached, no restart needed).
  • Full ssh_config(5) compatibility: Host glob/negation, all Match types (host, originalhost, user, localuser, exec, all, canonical, final), ProxyJump injection, hostname canonicalization, Include.
  • Resolution applies to both the primary host and jump hosts. Explicit form values still take precedence over ssh config defaults.
  • Drops the useSSHConfig toggle (resolution is always on). SSHConfiguration.port migrated to Int? so an explicit form value of 22 can override a non-22 config Port.

Architecture

Three layers, in TablePro/Core/SSH/:

SSHConfigParser  →  SSHConfigDocument  →  SSHConfigResolver  →  ResolvedSSHTarget
  (text → AST)       (cached blocks)       (runtime apply)        (concrete values)

New types: SSHConfigDocument, SSHConfigBlock, SSHConfigCriteria, HostPattern, MatchCondition, CanonicalizeMode, SSHDirective, ResolvedSSHTarget, ResolverEnvironment.

New components:

  • SSHConfigResolver — pure resolver, two-pass (Match canonical/final on second pass), explicit-form-wins precedence.
  • SSHConfigCache — actor with mtime-based invalidation; main config file's mtime always tracked.
  • SSHHostPatternMatcherfnmatch(3) glob + negation, mirrors OpenSSH match_pattern_list.
  • SSHHostnameCanonicalizergetaddrinfo + AI_CANONNAME for CanonicalizeHostname/CanonicalDomains.
  • SSHMatchExecutor — runs Match exec via /bin/sh -c with 5s timeout; replaces busy-poll with waitUntilExit + DispatchWorkItem timeout.

LibSSH2TunnelFactory.buildAuthenticatedChain now resolves the primary host and every jump hop up front; auth helpers take ResolvedSSHTarget. Removed the duplicate findEntry/resolveIdentityFiles paths.

Test plan

  • swiftlint lint TablePro/Core/SSH/ TableProTests/Core/SSH/ — 0 violations in new files.
  • xcodebuild build — clean.
  • xcodebuild test -only-testing:TableProTests/SSHConfigResolverTests — 17 pass.
  • xcodebuild test across all SSH suites — 274 pass / 0 fail (excluding 1 pre-existing flaky HostKeyStoreTests/testPortDifferentiation unrelated to this PR).
  • New tests cover: alias resolution, explicit-form precedence, ProxyJump injection, Match host/originalhost/exec/canonical/final semantics, two-pass canonicalization, glob patterns, jump-host alias resolution, Match final overriding first-pass values, mtime-based cache invalidation, Match exec exit codes and timeout.
  • Manual: type aia-bastion (or any Host alias) in the SSH host field, leave port/user empty, click Test Connection — should succeed.
  • Manual: configure Host alias / ProxyJump bastion in ~/.ssh/config, leave jump hosts empty in form — tunnel should chain through bastion.

Migration notes

  • SSHConfiguration.port and SSHJumpHost.port are now Int?. Old JSON with explicit "port": 22 decodes to Optional(22) and is treated as an explicit override.
  • useSSHConfig field removed from model, storage (StoredConnection JSON), CloudKit sync, deeplink, exports, and all three foreign-app importers (TablePlus / DBeaver / Sequel Ace). Old JSON containing the field decodes silently (CodingKey removed). Old app versions writing to the same iCloud account keep working.

Files

40 files changed: 1944 insertions, 471 deletions. 7 new source files in TablePro/Core/SSH/, 4 new test files in TableProTests/Core/SSH/.

@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin datlechin merged commit a9f3d7d into main May 4, 2026
2 checks passed
@datlechin datlechin deleted the fix/ssh-config-runtime-resolution-977 branch May 4, 2026 06:14
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.

SSH tunnel does not work

1 participant