mirror of
https://github.com/stablyai/orca.git
synced 2026-10-20 16:03:20 +00:00
* 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>
162 lines
5.3 KiB
TypeScript
162 lines
5.3 KiB
TypeScript
import type { SshTarget } from '../../shared/ssh-types'
|
|
import { getControlSocketPath, type SystemSshResolvedConfig } from './ssh-control-socket'
|
|
|
|
export type SystemSshBuildArgsOptions = {
|
|
resolvedConfig?: SystemSshResolvedConfig | null
|
|
disableControlMaster?: boolean
|
|
suppressOrcaControlMaster?: boolean
|
|
}
|
|
|
|
export function buildSshArgs(target: SshTarget, options?: SystemSshBuildArgsOptions): string[] {
|
|
const args: string[] = []
|
|
|
|
args.push('-o', 'BatchMode=no')
|
|
// Forward stdin/stdout for relay communication
|
|
args.push('-T')
|
|
|
|
// Why: ControlMaster multiplexes all SSH exec commands over a single connection,
|
|
// eliminating the ~9s handshake overhead per command. Without this, each
|
|
// spawnSystemSshCommand call opens a new TCP connection.
|
|
const forceDisableControlMaster =
|
|
options?.disableControlMaster === true || target.systemSshConnectionReuse === false
|
|
const controlPath = getOrcaControlSocketPath(target, options)
|
|
if (forceDisableControlMaster) {
|
|
// Why: muxed OpenSSH forwards remain registered on the master after the
|
|
// client exits. Also honors the per-target compatibility opt-out even if
|
|
// a broad Host * ssh_config block enables multiplexing.
|
|
args.push('-S', 'none')
|
|
} else if (controlPath) {
|
|
args.push('-o', 'ControlMaster=auto')
|
|
args.push('-o', `ControlPath=${controlPath}`)
|
|
// Why: keep master alive 300s after last command so rapid reconnects
|
|
// (e.g. on tab focus) skip re-handshake without holding a process open.
|
|
args.push('-o', 'ControlPersist=300')
|
|
args.push('-o', 'ServerAliveInterval=15')
|
|
args.push('-o', 'ServerAliveCountMax=3')
|
|
}
|
|
|
|
const useConfigHost = shouldUseOpenSshConfigHost(target)
|
|
|
|
if (!useConfigHost && target.port !== 22) {
|
|
args.push('-p', String(target.port))
|
|
}
|
|
|
|
if (!useConfigHost && target.identityFile) {
|
|
args.push('-i', target.identityFile)
|
|
}
|
|
|
|
if (!useConfigHost && target.identityAgent) {
|
|
args.push('-o', `IdentityAgent=${target.identityAgent}`)
|
|
}
|
|
|
|
if (!useConfigHost && target.identitiesOnly) {
|
|
args.push('-o', 'IdentitiesOnly=yes')
|
|
}
|
|
|
|
if (!useConfigHost && target.jumpHost) {
|
|
args.push('-J', target.jumpHost)
|
|
}
|
|
|
|
if (!useConfigHost && target.proxyCommand) {
|
|
args.push('-o', `ProxyCommand=${target.proxyCommand}`)
|
|
}
|
|
|
|
const host = target.configHost || target.host
|
|
const userHost = target.username ? `${target.username}@${host}` : host
|
|
args.push('--', userHost)
|
|
|
|
return args
|
|
}
|
|
|
|
export function getOrcaControlSocketPath(
|
|
target: SshTarget,
|
|
options?: SystemSshBuildArgsOptions
|
|
): string | null {
|
|
if (shouldDisableOrcaControlMaster(target, options)) {
|
|
return null
|
|
}
|
|
return getControlSocketPath(target, options?.resolvedConfig)
|
|
}
|
|
|
|
export function getSystemSshBuildArgsFromOperationOptions(
|
|
options: SystemSshBuildArgsOptions | undefined
|
|
): SystemSshBuildArgsOptions | undefined {
|
|
const buildArgsOptions: SystemSshBuildArgsOptions = {}
|
|
if (options?.resolvedConfig !== undefined) {
|
|
buildArgsOptions.resolvedConfig = options.resolvedConfig
|
|
}
|
|
if (options?.disableControlMaster === true) {
|
|
buildArgsOptions.disableControlMaster = true
|
|
}
|
|
if (options?.suppressOrcaControlMaster === true) {
|
|
buildArgsOptions.suppressOrcaControlMaster = true
|
|
}
|
|
return Object.keys(buildArgsOptions).length === 0 ? undefined : buildArgsOptions
|
|
}
|
|
|
|
function shouldDisableOrcaControlMaster(
|
|
target: SshTarget,
|
|
options?: SystemSshBuildArgsOptions
|
|
): boolean {
|
|
// Why: unresolved ssh_config aliases could otherwise share one Orca socket
|
|
// while OpenSSH routes them through mutable HostName/ProxyJump settings.
|
|
const unresolvedConfigBackedTarget =
|
|
isOpenSshConfigBackedTarget(target) && options?.resolvedConfig == null
|
|
return (
|
|
options?.disableControlMaster === true ||
|
|
options?.suppressOrcaControlMaster === true ||
|
|
target.systemSshConnectionReuse === false ||
|
|
unresolvedConfigBackedTarget ||
|
|
hasUserConfiguredControlMaster(options?.resolvedConfig)
|
|
)
|
|
}
|
|
|
|
function hasUserConfiguredControlMaster(
|
|
resolvedConfig: SystemSshResolvedConfig | null | undefined
|
|
): boolean {
|
|
if (!resolvedConfig) {
|
|
return false
|
|
}
|
|
// Why: ControlPersist/ControlPath alone can reuse a master someone else
|
|
// created, but they do not create the setup-burst master Orca needs.
|
|
return (
|
|
hasEnabledControlMaster(resolvedConfig.controlMaster) &&
|
|
hasEnabledControlPath(resolvedConfig.controlPath)
|
|
)
|
|
}
|
|
|
|
function hasEnabledControlMaster(value: string | undefined): boolean {
|
|
const normalized = value?.trim().toLowerCase()
|
|
return (
|
|
normalized != null &&
|
|
normalized !== '' &&
|
|
normalized !== '0' &&
|
|
normalized !== 'no' &&
|
|
normalized !== 'false'
|
|
)
|
|
}
|
|
|
|
function hasEnabledControlPath(value: string | undefined): boolean {
|
|
const normalized = value?.trim().toLowerCase()
|
|
return normalized != null && normalized !== '' && normalized !== 'none'
|
|
}
|
|
|
|
function shouldUseOpenSshConfigHost(target: SshTarget): boolean {
|
|
if (!target.configHost) {
|
|
return false
|
|
}
|
|
return isOpenSshConfigBackedTarget(target)
|
|
}
|
|
|
|
function isOpenSshConfigBackedTarget(target: SshTarget): boolean {
|
|
if (target.source === 'ssh-config') {
|
|
return true
|
|
}
|
|
if (target.source === 'manual') {
|
|
return false
|
|
}
|
|
// Why: legacy imported aliases have a distinct configHost; manual targets
|
|
// historically stored configHost=host and still need explicit -p/-i args.
|
|
return Boolean(target.configHost && target.configHost !== target.host)
|
|
}
|