feat(ssh): add None auth method for passwordless tunnels#1909
Conversation
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Workflows to automatically generate PRs for you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 3620acfb7c
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| case privateKey = "Private Key" | ||
| case sshAgent = "SSH Agent" | ||
| case keyboardInteractive = "Keyboard Interactive" | ||
| case none = "None" |
There was a problem hiding this comment.
Add None to the shared SSH auth model
This introduces a new persisted raw value ("None") for Mac connections, but the shared model used by TableProMobile/TableProSync (Packages/TableProCore/Sources/TableProModels/SSHConfiguration.swift) still only recognizes password/privateKey/sshAgent/keyboardInteractive and falls back to password for anything else. A no-auth SSH connection synced or imported into the mobile/shared path will therefore be decoded as password auth, and a later mobile sync/export can write it back that way, silently downgrading a Tailscale/no-auth tunnel so it fails on Mac. Please preserve/add the None case in the shared model and mobile handling, even if mobile only reports it as unsupported.
Useful? React with 👍 / 👎.
Closes discussion #1907.
Problem
You can't reach a database over an SSH tunnel when the SSH host uses Tailscale SSH. Tailscale authenticates the peer at the tailnet layer, so its SSH server accepts the connection with the SSH
nonemethod and expects no password or key. Leaving TablePro's SSH password blank fails with "SSH password rejected", because the.passwordarm ofbuildAuthenticatorthrows whencredentials.sshPassword == nil, before any libssh2 call. TablePro's auth model had no way to authenticate with no credentials.Fix
Add an explicit
NoneSSH auth method backed by aNoneAuthenticator.NoneAuthenticatoruses the documented libssh2 idiom:libssh2_userauth_list()sends anoneuserauth request on the wire; if the server accepts (Tailscale accept mode), it returnsNULLandlibssh2_userauth_authenticated()returns 1, so the tunnel is up. If the server offers methods instead, it throws a new.passwordlessRejectedreason that tells you to pick a method that sends credentials. Auth runs in blocking mode, so there is noEAGAINcase to handle.Both SSH forms (inline and profile editor) get an explicit
Nonebranch that hides the credential fields and shows a short hint. An explicit method beats a silently-blank password: it is discoverable, it removes the ambiguity between "I forgot my password" and "this host needs none", and it is a deliberate contract rather than an accident of libssh2 behavior (which broke for blank-password clients on Tailscale 1.80.0).Hardening in the same area
authMethoddecode.SSHConfiguration.init(from:)decodedauthMethodwith a throwingdecode, and because the tunnel JSON is read undertry?, an older app reading aNoneconnection synced from a newer Mac silently decoded the whole SSH config as disabled, dropping host, port, and jump hosts. It now falls back toPasswordand keeps the tunnel. This caps the blast radius of every future auth-method addition.sshNoAuth=trueflag so aNoneconfig exported to ascheme+ssh://URL or deeplink round-trips instead of reverting toPassword.Tests
Nonebuilds aNoneAuthenticator; the nil-password guard throws (regression for the reported bug)..passwordlessRejectedproduces a distinct, non-empty message.authMethoddecodes toPasswordand preserves host/port;Noneround-trips through Codable.sshNoAuth.Docs / CHANGELOG
docs/databases/ssh-tunneling.mdxdocuments theNonemethod and its Tailscale use.Addedentry under[Unreleased].Notes
nonehandshake needs a real Tailscale SSH host, so it is covered here by the build and unit tests; worth a manual check on a tailnet.SSHJumpHostuses a separateSSHJumpAuthMethod(private key / agent only), and Tailscale SSH is the tunnel target, not the bastion.SSHAuthMethodis app-level.