Skip to content

Commit c99cade

Browse files
committed
Reject user interaction in runner_ssh_tunnel
Fixes: #3715
1 parent 7e3969c commit c99cade

File tree

2 files changed

+30
-0
lines changed

2 files changed

+30
-0
lines changed

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

Lines changed: 29 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,11 +80,22 @@ 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)
8594
self.reverse_forwarded_sockets = list(reverse_forwarded_sockets)
8695
self.options = options
96+
# A copy of options with names normalized to lowercase. Used only internally, the actual
97+
# ssh command is built from user-provided options as is.
98+
self.options_normalized = {k.lower(): v for k, v in options.items()}
8799
self.port = port
88100
self.ssh_config_path = normalize_path(ssh_config_path)
89101
temp_dir = tempfile.TemporaryDirectory()
@@ -101,6 +113,7 @@ def __init__(
101113
proxy_identity, f"proxy_identity_{proxy_index}"
102114
)
103115
self.ssh_proxies.append((proxy_params, proxy_identity_path))
116+
self.batch_mode = batch_mode
104117
self.log_path = normalize_path(os.path.join(temp_dir.name, "tunnel.log"))
105118
self.ssh_client_info = get_ssh_client_info()
106119
self.ssh_exec_path = str(self.ssh_client_info.path)
@@ -145,6 +158,14 @@ def open_command(self) -> List[str]:
145158
command += ["-p", str(self.port)]
146159
for k, v in self.options.items():
147160
command += ["-o", f"{k}={v}"]
161+
if self.batch_mode:
162+
command += ["-o", "BatchMode=yes"]
163+
if "serveraliveinterval" not in self.options_normalized:
164+
# Revert Debian-specific patch effect:
165+
# > The default is 0, indicating that these messages will not be sent
166+
# > to the server, or 300 if the BatchMode option is set (Debian-specific).
167+
# https://salsa.debian.org/ssh-team/openssh/-/blob/d87b69641b533b892b87e2eea02dbee796682d64/debian/patches/keepalive-extensions.patch#L69-77
168+
command += ["-o", "ServerAliveInterval=0"]
148169
if proxy_command := self._get_proxy_command():
149170
command += ["-o", proxy_command]
150171
for socket_pair in self.forwarded_sockets:
@@ -290,6 +311,14 @@ def _build_proxy_command(
290311
"-o",
291312
"UserKnownHostsFile=/dev/null",
292313
]
314+
if self.batch_mode:
315+
# ServerAliveInterval is explained in the open_command() comment
316+
command += [
317+
"-o",
318+
"BatchMode=yes",
319+
"-o",
320+
"ServerAliveInterval=0",
321+
]
293322
if prev_proxy_command is not None:
294323
command += ["-o", prev_proxy_command.replace("%", "%%")]
295324
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)