Skip to content

Commit

Permalink
#4288 add 'show-ssl' and list new subcommands in usage info
Browse files Browse the repository at this point in the history
  • Loading branch information
totaam committed Sep 12, 2024
1 parent 25bfef2 commit 89ccafc
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 39 deletions.
16 changes: 9 additions & 7 deletions xpra/net/ssh/paramiko_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@
from xpra.platform.info import get_username
from xpra.scripts.config import str_to_bool, TRUE_OPTIONS
from xpra.scripts.pinentry import input_pass, confirm
from xpra.net.ssh.util import get_default_keyfiles
from xpra.net.ssh.util import get_default_keyfiles, LOG_EOF
from xpra.net.bytestreams import SocketConnection, SOCKET_TIMEOUT
from xpra.util.thread import start_thread
from xpra.exit_codes import ExitCode
from xpra.util.io import load_binary_file, stderr_print, umask_context
from xpra.common import noerr
from xpra.util.str_fn import csv
from xpra.util.env import envint, envbool, envfloat
from xpra.util.env import envint, envbool, envfloat, first_time
from xpra.log import Logger

# pylint: disable=import-outside-toplevel
Expand Down Expand Up @@ -98,7 +98,8 @@ def _stderr_reader(self) -> None:
while self.active:
v = stderr.readline()
if not v:
log.info("SSH EOF on stderr of %s", chan.get_name())
if LOG_EOF:
log.info("SSH EOF on stderr of %s", chan.get_name())
break
s = v.rstrip(b"\n\r").decode()
if s:
Expand Down Expand Up @@ -172,10 +173,11 @@ def safe_lookup(config_obj, host: str) -> dict:
log.warn(" (looks like a 'paramiko' distribution packaging issue)")
except KeyError as e:
log("%s.lookup(%s)", config_obj, host, exc_info=True)
log.info(f"paramiko ssh config lookup error for host {host!r}:")
log.info(" %s: %s", type(e), e)
log.info(" the paramiko project looks unmaintained:")
log.info(" https://github.com/paramiko/paramiko/pull/2338")
log.info(f"paramiko ssh config lookup error for host {host!r}")
if first_time("paramiko-#2338"):
log.info(" %s: %s", type(e), e)
log.info(" the paramiko project looks unmaintained:")
log.info(" https://github.com/paramiko/paramiko/pull/2338")
return {}


Expand Down
5 changes: 4 additions & 1 deletion xpra/net/ssh/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@

import os

from xpra.util.env import osexpand
from xpra.util.env import osexpand, envbool


LOG_EOF = envbool("XPRA_SSH_LOG_EOF", True)


def get_default_keyfiles() -> list[str]:
Expand Down
10 changes: 10 additions & 0 deletions xpra/net/ssl_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -623,3 +623,13 @@ def gen_ssl_cert() -> tuple[str, str]:
os.fchmod(f.fileno(), 0o600)
f.write(sslcert)
return keypath, certpath


def strip_cert(data: bytes) -> bytes:
BEGIN = b"-----BEGIN CERTIFICATE-----"
if data.find(BEGIN) >= 0:
data = BEGIN + data.split(BEGIN, 1)[1]
END = b"-----END CERTIFICATE-----"
if data.find(END) > 0:
data = data.split(END, 1)[0] + END + b"\n"
return data
93 changes: 62 additions & 31 deletions xpra/scripts/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ def run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: str,

# configure default logging handler:
if POSIX and getuid() == options.uid == 0 and mode not in (
"proxy", "autostart", "showconfig", "setup-ssl",
"proxy", "autostart", "showconfig", "setup-ssl", "show-ssl",
) and not NO_ROOT_WARNING:
warn("\nWarning: running as root\n")

Expand Down Expand Up @@ -472,7 +472,7 @@ def run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: str,
"keyboard", "gtk-info", "gui-info", "network-info",
"compression", "packet-encoding", "path-info",
"printing-info", "version-info", "toolbox",
"initenv", "setup-ssl",
"initenv", "setup-ssl", "show-ssl",
"auth", "showconfig", "showsetting",
"applications-menu", "sessions-menu",
"_proxy",
Expand Down Expand Up @@ -840,6 +840,8 @@ def do_run_mode(script_file: str, cmdline, error_cb, options, args, full_mode: s
return ExitCode.OK
if mode == "setup-ssl":
return setup_ssl(options, args, cmdline)
if mode == "show-ssl":
return show_ssl(options, args, cmdline)
if mode == "auth":
return run_auth(options, args)
if mode == "configure":
Expand Down Expand Up @@ -4339,39 +4341,45 @@ def err(*args) -> NoReturn:
raise InitException(*args)


def get_remote_proxy_command_output(options, args, cmdline, subcommand="setup-ssl") -> tuple[dict, bytes]:
if len(args) != 1:
raise InitExit(ExitCode.FAILURE, "a single optional argument may be specified")
arg = args[0]
disp = parse_display_name(error_handler, options, arg, cmdline)
if disp.get("type", "") != "ssh":
raise InitExit(ExitCode.FAILURE, "argument must be an ssh URL")
disp["display_as_args"] = []
disp["proxy_command"] = [subcommand, ]

from xpra.net.ssh import util
util.LOG_EOF = False

def ssh_fail(*_args) -> NoReturn:
sys.exit(int(ExitCode.SSH_FAILURE))

def ssh_log(*args) -> None:
log = Logger("ssh")
log.debug(*args)

conn = connect_to(disp, options, debug_cb=ssh_log, ssh_fail_cb=ssh_fail)
data = b""
until = monotonic() + 30
while monotonic() < until:
bdata = conn.read(4096)
if not bdata:
break
data += bdata
noerr(conn.close)
return disp, data


def setup_ssl(options, args, cmdline) -> ExitValue:
from xpra.net.ssl_util import gen_ssl_cert, save_ssl_config_file
from xpra.net.ssl_util import gen_ssl_cert, save_ssl_config_file, strip_cert
if args:
if len(args) != 1:
raise InitExit(ExitCode.FAILURE, "a single optional argument may be specified")
arg = args[0]
disp = parse_display_name(error_handler, options, arg, cmdline)
if disp.get("type", "") != "ssh":
raise InitExit(ExitCode.FAILURE, "argument must be an ssh URL")
disp["display_as_args"] = []
disp["proxy_command"] = ["setup-ssl", ]

def ssh_fail(*_args) -> NoReturn:
sys.exit(int(ExitCode.SSH_FAILURE))
conn = connect_to(disp, options, debug_cb=noop, ssh_fail_cb=ssh_fail)
data = b""
until = monotonic() + 30
while monotonic() < until:
bdata = conn.read(4096)
if not bdata:
break
data += bdata
noerr(conn.close)
disp, data = get_remote_proxy_command_output(options, args, cmdline, "setup-ssl")
if not data:
raise InitExit(ExitCode.FAILURE, "no certificate data received, check the command output")
# strip any pollution from ssh output:
BEGIN = b"-----BEGIN CERTIFICATE-----"
if data.find(BEGIN) >= 0:
data = BEGIN + data.split(BEGIN, 1)[1]
END = b"-----END CERTIFICATE-----"
if data.find(END) > 0:
data = data.split(END, 1)[0] + END + b"\n"

data = strip_cert(data)
host = disp["host"]
save_ssl_config_file(host, port=0, filename="cert.pem", fileinfo="certificate", filedata=data)
return ExitCode.OK
Expand All @@ -4382,6 +4390,29 @@ def ssh_fail(*_args) -> NoReturn:
return 0


def show_ssl(options, args, cmdline) -> ExitValue:
from xpra.net.ssl_util import find_ssl_cert, strip_cert, KEY_FILENAME, CERT_FILENAME
if args:
_disp, data = get_remote_proxy_command_output(options, args, cmdline, "show-ssl")
if not data:
raise InitExit(ExitCode.FAILURE, "no certificate data received, check the command output")
cert = strip_cert(data)
else:
log = Logger("ssl")
keypath = find_ssl_cert(KEY_FILENAME)
certpath = find_ssl_cert(CERT_FILENAME)
if not keypath or not certpath:
log.info("no certificate found")
return ExitCode.NO_DATA
log.info("found an existing SSL certificate:")
log.info(f" {keypath!r}")
log.info(f" {certpath!r}")
from xpra.util.io import load_binary_file
cert = load_binary_file(certpath)
sys.stdout.write(cert.decode("latin1"))
return ExitCode.OK


def run_showconfig(options, args) -> ExitValue:
log = get_logger()
d = dict_to_validated_config({})
Expand Down
2 changes: 2 additions & 0 deletions xpra/scripts/parsing.py
Original file line number Diff line number Diff line change
Expand Up @@ -826,6 +826,8 @@ def get_usage() -> list[str]:
]
command_options += [
"showconfig",
"setup-ssl",
"show-ssl",
"list",
"list-sessions",
"list-windows",
Expand Down

0 comments on commit 89ccafc

Please sign in to comment.