1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-11-09 11:46:49 +00:00
webvirtcloud/console/sshtunnels.py
catborise 294b008811 fix(console): an SSH compute's VNC console goes through ssh -W
- The tunnel is `ssh -W` to the console's address or Unix socket: the
  compute needs no nc and runs no shell. The old tunnel, copied from
  virt-manager, built a shell script with the console's address and socket
  path in it and eval'd it on the compute, and always passed "-p 22",
  overriding a Port in the SSH configuration.
- On an SSH compute the console always goes through the tunnel, also when
  it listens on a network address: it was sent there unencrypted.
- ssh never prompts (BatchMode) and gives up connecting after 10 seconds;
  when it fails, what it said is logged. The tunnel ends with the session.
2026-10-26 19:21:22 +03:00

41 lines
1.6 KiB
Python

"""The VNC console of an SSH compute, carried by the SSH connection: `ssh -W`
forwards its standard input and output to the console's address or Unix
socket on the compute, so the compute needs no nc and runs no shell."""
import ipaddress
import socket
import subprocess
def tunnel_target(address, port, unix_socket):
"""Where on the compute ssh forwards to: the Unix socket, or the address
and port, a wildcard or missing address meaning the compute's loopback."""
if unix_socket:
return unix_socket
address = {None: "127.0.0.1", "": "127.0.0.1", "0.0.0.0": "127.0.0.1", "::": "::1"}.get(address, address)
try:
if ipaddress.ip_address(address).version == 6:
address = f"[{address}]"
except ValueError: # a host name
pass
return f"{address}:{port}"
def open_tunnel(host, user, target):
"""A socket connected to target on host, and the ssh process carrying it.
ssh never asks for anything: a missing key or an unknown host key fails."""
ours, theirs = socket.socketpair()
argv = ["ssh", "-o", "BatchMode=yes", "-o", "ConnectTimeout=10", "-W", target]
if user:
argv += ["-l", user]
process = subprocess.Popen(argv + ["--", host], stdin=theirs, stdout=theirs, stderr=subprocess.PIPE)
theirs.close()
return ours, process
def close_tunnel(process):
"""End the ssh process; its error output, if it failed."""
if process.poll() is None:
process.terminate()
_, error = process.communicate()
return error.decode(errors="replace").strip() if process.returncode not in (0, -15) else ""