* feat(ssh): support Kerberos/GSSAPI hosts via the system OpenSSH transport
ssh2 has no gssapi-with-mic support, and adding it would mean forking its
protocol layer plus packaging the kerberos native module for three
platforms. Instead, route GSSAPI hosts through the existing system-OpenSSH
transport, which delegates Kerberos (tickets, SSPI on Windows) to the
platform ssh binary.
Two tiers, because RHEL-family distros enable GSSAPIAuthentication
globally in /etc/ssh/ssh_config and ssh -G therefore reports it for every
host:
- Targets whose ~/.ssh/config Host block explicitly sets
GSSAPIAuthentication yes (imported as target.gssapiAuthentication) try
system ssh first, falling through to ssh2 so key auth and credential
prompts still work when no ticket is available.
- When ssh2 exhausts key/agent auth and the ssh -G-resolved config
enables GSSAPI, retry over system ssh before prompting for credentials,
so Kerberos-only hosts on distro-default configs connect without a
password prompt. Hosts where keys work never leave the ssh2 path.
Manual targets flagged for GSSAPI pass -o GSSAPIAuthentication=yes
explicitly since they bypass ssh_config. Both tiers work headless (no
credential callbacks required).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ssh): harden GSSAPI transport selection (review fixes for PR #7507)
Review fixes on top of the Kerberos/GSSAPI feature branch (s546126/kerberos-ssh):
- HIGH: reset useSystemSshTransport on the ssh2 fall-through. doSystemSshProbe
sets the flag before spawnSystemSshCommand, which throws synchronously when no
system ssh binary is on PATH (outside the probe try/catch). The proactive
fall-through previously reset only 2 of 3 transport fields, so exec/sftp kept
routing through the failed transport - breaking GSSAPI on Windows-with-Git-ssh
and headless Linux.
- MEDIUM: throw a cancellation error (not the stale ssh2 authError) when a
disconnect supersedes the reactive probe mid-flight, and guard connect()'s
catch on disposed, so a deliberate disconnect is not overwritten with
auth-failed.
- MEDIUM: skip the encrypted-key passphrase prompt when the GSSAPI fallback
applies, so a Kerberos ticket is tried before prompting; the general prompt
still fires if the probe fails.
Adds 3 mutation-verified regression tests and hardens two existing tests to
assert the probe actually ran. Not connected to any PR remote.
Co-authored-by: Orca <help@stably.ai>
* fix(ssh): isolate GSSAPI system transport
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: s546126 <268420947+s546126@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>
* feat(ssh): add ControlMaster multiplexing for system SSH transport
System SSH transport spawns a new OpenSSH process per exec command
(platform detect, relay install check, node resolution, relay launch,
socket probe). Each process pays the full SSH handshake cost — ~9s on
Uber devpods — making a typical relay connect take 54s+ and reliably
exceeding the 15s startup reconnect budget.
Add SSH ControlMaster multiplexing via a per-target socket in
$TMPDIR/orca-ssh-ctl/<hash>.sock. The first command establishes the
master; subsequent commands reuse it at ~100ms per exec instead of ~9s.
ControlPersist=300 keeps the master alive after commands exit so rapid
reconnects (e.g. on tab focus) also benefit. Windows is excluded since
OpenSSH's ControlMaster support there is limited.
* fix(ssh): address ControlMaster key collision and directory permission risks
- Use target.id in the socket key so distinct SSH targets can never
collide even when configHost/port/user happen to match
- Switch from SHA1 to SHA256 and extend hash slice from 12 to 16 chars
- Stat the control-socket directory after mkdirSync to reject pre-existing
dirs that are symlinks, foreign-owned, or have group/other write bits
(mkdirSync mode is ignored on pre-existing dirs)
- Update two tests that used exact spawn-arg arrays; replace with
ordering assertions (forward flags before --) that stay correct
regardless of which extra ControlMaster options are injected
* fix(ssh): bind ControlPath identity to route and reject symlinked ctl dir
Fold proxyCommand/jumpHost/identity fields into the ControlPath hash so a
target whose route is edited no longer reuses a still-alive master built on
the old route. Switch the control-socket dir check from statSync to lstatSync
so a planted symlink fails the directory validation outright.
* test(ssh): drop tautological argv re-assertion in spawn checks
The toHaveBeenCalledWith re-passed the args array extracted from the same
mock call, making that argument position always pass. argv content is
already verified by the index-ordering assertions above; use expect.any(Array)
so the spawn check only claims what it actually verifies (binary path, stdio).
* fix(ssh): harden system ssh connection reuse
Co-authored-by: Orca <help@stably.ai>
* test(ssh): isolate control socket runtime dir
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Test <test@example.com>
Co-authored-by: Jinwoo-H <jinwoo0825@gmail.com>
Co-authored-by: Orca <help@stably.ai>