Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Makefile.toml
Original file line number Diff line number Diff line change
Expand Up @@ -337,7 +337,7 @@ cd standalone-py/python/install
cp . ../../../py39
cd ../../../
exec --fail-on-error ${PYTHON} ./get-pip.py
exec --fail-on-error ${PYTHON} -m pip install wheel flit . ".[test]"
exec --fail-on-error ${PYTHON} -m pip install wheel flit . ".[test]" ".[ssh-tunnel]"
cm_run_task generate-resources
'''

Expand Down
8 changes: 8 additions & 0 deletions console_backend/src/cli_options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,14 @@ pub struct CliOptions {
/// Enable QML Debugging and profiling.
#[clap(long, hide = true)]
pub qmldebug: bool,

/// SSH tunnel to a jumphost specified ([username]:[password]@some.fqdn)
#[clap(long, hide = true)]
pub ssh_tunnel: Option<PathBuf>,

/// SSH tunnel forward port of remote IP and port to localhost (some.fqdn:port)
#[clap(long, hide = true)]
pub ssh_remote_bind_address: Option<PathBuf>,
}

impl CliOptions {
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ test = [
"py2many >=0.3",
"types-requests ~= 2.28.11",
]
ssh-tunnel = ["sshtunnel >= 0.4.0"]

[build-system]
requires = [
Expand Down
1 change: 1 addition & 0 deletions swift-toolbox.pyproject
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"swiftnav_console/solution_position_tab.py",
"swiftnav_console/solution_table.py",
"swiftnav_console/solution_velocity_tab.py",
"swiftnav_console/ssh_tunnel.py",
"swiftnav_console/status_bar.py",
"swiftnav_console/tracking_signals_tab.py",
"swiftnav_console/tracking_sky_plot_tab.py",
Expand Down
33 changes: 33 additions & 0 deletions swiftnav_console/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,14 @@
import sys
import time

try:
import sshtunnel # type: ignore # pylint: disable=unused-import
from . import ssh_tunnel

FEATURE_SSHTUNNEL = True
except ImportError:
FEATURE_SSHTUNNEL = False

from typing import Optional, Tuple

import capnp # type: ignore
Expand Down Expand Up @@ -639,6 +647,11 @@ def handle_cli_arguments(args: argparse.Namespace, globals_: QObject):
globals_.setProperty("width", args.width) # type: ignore
if args.show_file_connection:
globals_.setProperty("showFileConnection", True) # type: ignore
try:
if args.ssh_tunnel:
ssh_tunnel.setup(args.ssh_tunnel, args.ssh_remote_bind_address)
except AttributeError:
pass


def start_splash_linux():
Expand Down Expand Up @@ -702,11 +715,26 @@ def main(passed_args: Optional[Tuple[str, ...]] = None) -> int:
parser.add_argument("--height", type=int)
parser.add_argument("--width", type=int)
parser.add_argument("--qmldebug", action="store_true")
if FEATURE_SSHTUNNEL:
parser.add_argument("--ssh-tunnel", type=str, default=None)
parser.add_argument("--ssh-remote-bind-address", type=str, default=None)

args_main, unknown_args = parser.parse_known_args()
for unknown_arg in unknown_args:
for tunnel_arg in ("--ssh-tunnel", "--ssh-remote-bind-address"):
if tunnel_arg in unknown_arg:
parser.error(
f"Option {tunnel_arg} unsupported.\n"
"The --ssh-tunnel and --ssh-remote-bind-address "
"arguments require the `sshtunnel` python module."
)

if args_main.debug_with_no_backend and args_main.read_capnp_recording is None:
parser.error("The --debug-with-no-backend argument requires the --read-capnp-recording argument.")

if FEATURE_SSHTUNNEL:
ssh_tunnel.validate(args_main, parser)

found_help_arg = False
for arg in unknown_args:
if arg in HELP_CLI_ARGS:
Expand Down Expand Up @@ -846,6 +874,11 @@ def handle_qml_load_errors(obj, _url):

endpoint_main.shutdown()
backend_msg_receiver.join()
try:
# Stop the sshtunnel server if there is one.
sshtunnel_server.stop() # type: ignore
except NameError:
pass

return 0

Expand Down
86 changes: 86 additions & 0 deletions swiftnav_console/ssh_tunnel.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import argparse
import sshtunnel # type: ignore

_default_tunnel_port = 22
_default_remote_bind_port = 55555


def validate(args: argparse.Namespace, parser: argparse.ArgumentParser):
if not (args.ssh_remote_bind_address or args.ssh_tunnel):
return

if bool(args.ssh_remote_bind_address) != bool(args.ssh_tunnel):
parser.error(
f"""The --ssh-tunnel and --ssh-remote-bind-address options must be used together.
--ssh-tunnel option format: [user]:[password]@example.com[:port] (default port {_default_tunnel_port})
--ssh-remote-bind-address format: example.com[:port] (default port {_default_remote_bind_port})
"""
)

e2_g1_str = "expected 2, got 1"
try:
try:
(user_pw, host_port) = args.ssh_tunnel.split("@")
except ValueError as e:
if e2_g1_str not in str(e):
raise
(host_port,) = args.ssh_tunnel.split("@")
try:
(_, _) = user_pw.split(":")
except ValueError as e:
if e2_g1_str not in str(e):
raise
except UnboundLocalError:
pass
(_, _) = host_port.split(":")
except ValueError as e:
if e2_g1_str not in str(e):
parser.error(
f"""invalid --ssh-tunnel argument.
Please use format: [user]:[password]@example.com[:port] (default port {_default_tunnel_port})"""
)

try:
(_, _) = args.ssh_remote_bind_address.split(":")
except ValueError as e:
if e2_g1_str not in str(e):
parser.error(
f"""invalid --ssh-remote-bind-address argument.
Please use format: example.com[:port] (default port {_default_remote_bind_port})"""
)


def setup(tunnel_address: str, remote_bind_address: str):
username_password = ""
try:
(username_password, host_port) = tunnel_address.split("@")
except ValueError:
host_port = tunnel_address
password = None
try:
(username, password) = username_password.split(":")
except ValueError:
username = username_password
port = str(_default_tunnel_port)
try:
(host, port) = host_port.split(":")
except ValueError:
host = host_port

remote_bind_port = str(_default_remote_bind_port)
try:
(remote_bind_host, remote_bind_port) = remote_bind_address.split(":")
except ValueError:
remote_bind_host = remote_bind_address

global sshtunnel_server # pylint: disable=global-variable-undefined
# To debug this, set `logger` parameter to `sshtunnel.create_logger(None, "DEBUG")`
sshtunnel_server = sshtunnel.SSHTunnelForwarder( # type: ignore
(host, int(port)),
ssh_username=username,
ssh_password=password,
local_bind_address=("127.0.0.1", int(remote_bind_port)),
remote_bind_address=(remote_bind_host, int(remote_bind_port)),
# logger=sshtunnel.create_logger(None, "DEBUG"),
)
sshtunnel_server.start() # type: ignore