fix(ssh): verify mux readiness before reusing socket
Use ssh -O check in the first-use mux lock flow so commands only reuse a multiplexed socket after the control master is actually ready.
This commit is contained in:
parent
5c67766f41
commit
a13fb3cf00
2 changed files with 27 additions and 14 deletions
|
|
@ -28,15 +28,20 @@ public static function ensureMultiplexedConnection(Server $server): bool
|
||||||
|
|
||||||
public static function removeMuxFile(Server $server): void
|
public static function removeMuxFile(Server $server): void
|
||||||
{
|
{
|
||||||
$closeCommand = 'ssh -O exit -o ControlPath='.self::muxSocket($server).' ';
|
$closeCommand = self::muxControlCommand($server, 'exit');
|
||||||
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
|
||||||
$closeCommand .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
|
||||||
}
|
|
||||||
$closeCommand .= self::escapedUserAtHost($server);
|
|
||||||
|
|
||||||
Process::run($closeCommand);
|
Process::run($closeCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static function muxControlCommand(Server $server, string $operation): string
|
||||||
|
{
|
||||||
|
$command = "ssh -O {$operation} -o ControlPath=".self::muxSocket($server).' ';
|
||||||
|
if (data_get($server, 'settings.is_cloudflare_tunnel')) {
|
||||||
|
$command .= '-o ProxyCommand="cloudflared access ssh --hostname %h" ';
|
||||||
|
}
|
||||||
|
|
||||||
|
return $command.self::escapedUserAtHost($server);
|
||||||
|
}
|
||||||
|
|
||||||
public static function generateScpCommand(Server $server, string $source, string $dest): string
|
public static function generateScpCommand(Server $server, string $source, string $dest): string
|
||||||
{
|
{
|
||||||
$sshConfig = self::serverSshConfiguration($server);
|
$sshConfig = self::serverSshConfiguration($server);
|
||||||
|
|
@ -128,24 +133,31 @@ private static function withFirstUseMuxLock(Server $server, string $command): st
|
||||||
$lockDirectory = self::muxLockDirectory($server);
|
$lockDirectory = self::muxLockDirectory($server);
|
||||||
$lockTimeout = (int) config('constants.ssh.mux_lock_timeout');
|
$lockTimeout = (int) config('constants.ssh.mux_lock_timeout');
|
||||||
|
|
||||||
|
$checkCommand = self::muxControlCommand($server, 'check');
|
||||||
|
|
||||||
$script = <<<'SH'
|
$script = <<<'SH'
|
||||||
cmd=$1
|
cmd=$1
|
||||||
socket=$2
|
socket=$2
|
||||||
lock=$3
|
lock=$3
|
||||||
timeout=$4
|
timeout=$4
|
||||||
|
check=$5
|
||||||
|
|
||||||
run_command() {
|
run_command() {
|
||||||
sh -c "$cmd"
|
sh -c "$cmd"
|
||||||
}
|
}
|
||||||
|
|
||||||
if [ -S "$socket" ]; then
|
mux_ready() {
|
||||||
|
[ -S "$socket" ] && sh -c "$check" >/dev/null 2>&1
|
||||||
|
}
|
||||||
|
|
||||||
|
if mux_ready; then
|
||||||
run_command
|
run_command
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
||||||
waited=0
|
waited=0
|
||||||
while ! mkdir "$lock" 2>/dev/null; do
|
while ! mkdir "$lock" 2>/dev/null; do
|
||||||
if [ -S "$socket" ]; then
|
if mux_ready; then
|
||||||
run_command
|
run_command
|
||||||
exit $?
|
exit $?
|
||||||
fi
|
fi
|
||||||
|
|
@ -171,7 +183,7 @@ private static function withFirstUseMuxLock(Server $server, string $command): st
|
||||||
child=$!
|
child=$!
|
||||||
|
|
||||||
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
for _ in 1 2 3 4 5 6 7 8 9 10; do
|
||||||
if [ -S "$socket" ] || ! kill -0 "$child" 2>/dev/null; then
|
if mux_ready || ! kill -0 "$child" 2>/dev/null; then
|
||||||
break
|
break
|
||||||
fi
|
fi
|
||||||
sleep 0.1
|
sleep 0.1
|
||||||
|
|
@ -186,7 +198,8 @@ private static function withFirstUseMuxLock(Server $server, string $command): st
|
||||||
.escapeshellarg($command).' '
|
.escapeshellarg($command).' '
|
||||||
.escapeshellarg($muxSocket).' '
|
.escapeshellarg($muxSocket).' '
|
||||||
.escapeshellarg($lockDirectory).' '
|
.escapeshellarg($lockDirectory).' '
|
||||||
.escapeshellarg((string) $lockTimeout);
|
.escapeshellarg((string) $lockTimeout).' '
|
||||||
|
.escapeshellarg($checkCommand);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function escapedUserAtHost(Server $server): string
|
private static function escapedUserAtHost(Server $server): string
|
||||||
|
|
|
||||||
|
|
@ -71,8 +71,8 @@ function makeMuxServer(): Server
|
||||||
->toContain("-o ControlPath=/var/www/html/storage/app/ssh/mux/mux_{$server->uuid}")
|
->toContain("-o ControlPath=/var/www/html/storage/app/ssh/mux/mux_{$server->uuid}")
|
||||||
->toContain("/var/www/html/storage/app/ssh/mux/mux_{$server->uuid}.lock")
|
->toContain("/var/www/html/storage/app/ssh/mux/mux_{$server->uuid}.lock")
|
||||||
->toContain('-o ControlPersist=3600')
|
->toContain('-o ControlPersist=3600')
|
||||||
->not->toContain('ssh -fN')
|
->toContain('-O check')
|
||||||
->not->toContain('-O check');
|
->not->toContain('ssh -fN');
|
||||||
|
|
||||||
Process::assertNothingRan();
|
Process::assertNothingRan();
|
||||||
});
|
});
|
||||||
|
|
@ -105,8 +105,8 @@ function makeMuxServer(): Server
|
||||||
->toContain("-o ControlPath=/var/www/html/storage/app/ssh/mux/mux_{$server->uuid}")
|
->toContain("-o ControlPath=/var/www/html/storage/app/ssh/mux/mux_{$server->uuid}")
|
||||||
->toContain("/var/www/html/storage/app/ssh/mux/mux_{$server->uuid}.lock")
|
->toContain("/var/www/html/storage/app/ssh/mux/mux_{$server->uuid}.lock")
|
||||||
->toContain('-o ControlPersist=3600')
|
->toContain('-o ControlPersist=3600')
|
||||||
->not->toContain('ssh -fN')
|
->toContain('-O check')
|
||||||
->not->toContain('-O check');
|
->not->toContain('ssh -fN');
|
||||||
|
|
||||||
Process::assertNothingRan();
|
Process::assertNothingRan();
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue