Skip to content

Commit 725dfe2

Browse files
authored
Reject user interaction in runner_ssh_tunnel (#3716)
Fixes: #3715
1 parent bd42dfc commit 725dfe2

File tree

2 files changed

+27
-0
lines changed

2 files changed

+27
-0
lines changed

src/dstack/_internal/core/services/ssh/tunnel.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ def __init__(
7070
ssh_config_path: Union[PathLike, Literal["none"]] = "none",
7171
port: Optional[int] = None,
7272
ssh_proxies: Iterable[tuple[SSHConnectionParams, Optional[FilePathOrContent]]] = (),
73+
batch_mode: bool = False,
7374
):
7475
"""
7576
:param forwarded_sockets: Connections to the specified local sockets will be
@@ -79,6 +80,14 @@ def __init__(
7980
:param ssh_proxies: pairs of SSH connections params and optional identities,
8081
in order from outer to inner. If an identity is `None`, the `identity` param
8182
is used instead.
83+
:param batch_mode: If enabled, "user interaction such as password prompts and host key
84+
confirmation requests will be disabled", see `ssh_config(5)`, `BatchMode`.
85+
Although this is probably the desired behavior in all use cases, the default value
86+
is `False` for gradual adoption.
87+
Note, this option is only applied to the `destination` and `ssh_proxies`. If you
88+
configured `destination` with `ProxyJump` in the `ssh_config_path` config, the proxy
89+
jump connection will ignore this option -- in that case, you should replace `ProxyJump`
90+
with explicit `ProxyCommand=ssh [...] -o BatchMode=yes` in your config.
8291
"""
8392
self.destination = destination
8493
self.forwarded_sockets = list(forwarded_sockets)
@@ -101,6 +110,7 @@ def __init__(
101110
proxy_identity, f"proxy_identity_{proxy_index}"
102111
)
103112
self.ssh_proxies.append((proxy_params, proxy_identity_path))
113+
self.batch_mode = batch_mode
104114
self.log_path = normalize_path(os.path.join(temp_dir.name, "tunnel.log"))
105115
self.ssh_client_info = get_ssh_client_info()
106116
self.ssh_exec_path = str(self.ssh_client_info.path)
@@ -145,6 +155,14 @@ def open_command(self) -> List[str]:
145155
command += ["-p", str(self.port)]
146156
for k, v in self.options.items():
147157
command += ["-o", f"{k}={v}"]
158+
if self.batch_mode:
159+
command += ["-o", "BatchMode=yes"]
160+
if "serveraliveinterval" not in map(str.lower, self.options):
161+
# Revert Debian-specific patch effect:
162+
# > The default is 0, indicating that these messages will not be sent
163+
# > to the server, or 300 if the BatchMode option is set (Debian-specific).
164+
# https://salsa.debian.org/ssh-team/openssh/-/blob/d87b69641b533b892b87e2eea02dbee796682d64/debian/patches/keepalive-extensions.patch#L69-77
165+
command += ["-o", "ServerAliveInterval=0"]
148166
if proxy_command := self._get_proxy_command():
149167
command += ["-o", proxy_command]
150168
for socket_pair in self.forwarded_sockets:
@@ -290,6 +308,14 @@ def _build_proxy_command(
290308
"-o",
291309
"UserKnownHostsFile=/dev/null",
292310
]
311+
if self.batch_mode:
312+
# ServerAliveInterval is explained in the open_command() comment
313+
command += [
314+
"-o",
315+
"BatchMode=yes",
316+
"-o",
317+
"ServerAliveInterval=0",
318+
]
293319
if prev_proxy_command is not None:
294320
command += ["-o", prev_proxy_command.replace("%", "%%")]
295321
command += [

src/dstack/_internal/server/services/runner/ssh.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ def wrapper(
9696
forwarded_sockets=ports_to_forwarded_sockets(tunnel_ports_map),
9797
identity=identity,
9898
ssh_proxies=ssh_proxies,
99+
batch_mode=True,
99100
):
100101
return func(runner_ports_map, *args, **kwargs)
101102
except SSHError:

0 commit comments

Comments
 (0)