diff --git a/src/bridge/config.py b/src/bridge/config.py index 9283bf1..a7fac7b 100644 --- a/src/bridge/config.py +++ b/src/bridge/config.py @@ -107,6 +107,7 @@ def _parse_tunnel(name: str, data: dict) -> TunnelConfig: reconnect=reconnect, health_check=health_check, direction=direction, + remote_host=str(data.get("remote_host", "127.0.0.1")), cert_command=cert_command, ) diff --git a/src/bridge/manager.py b/src/bridge/manager.py index 7220fc9..f6e1df4 100644 --- a/src/bridge/manager.py +++ b/src/bridge/manager.py @@ -29,9 +29,9 @@ def build_ssh_command(cfg: TunnelConfig, cert_path: Optional[Path] = None) -> Li """Build the SSH tunnel command (reverse -R or local -L).""" key = os.path.expanduser(cfg.ssh_key) if cfg.direction == "local": - forward_flag = ["-L", f"{cfg.local_port}:127.0.0.1:{cfg.remote_port}"] + forward_flag = ["-L", f"{cfg.local_port}:{cfg.remote_host}:{cfg.remote_port}"] else: - forward_flag = ["-R", f"{cfg.remote_port}:127.0.0.1:{cfg.local_port}"] + forward_flag = ["-R", f"{cfg.remote_port}:{cfg.remote_host}:{cfg.local_port}"] cmd = [ "ssh", "-N", diff --git a/src/bridge/models.py b/src/bridge/models.py index 2a0ec8f..b4f3253 100644 --- a/src/bridge/models.py +++ b/src/bridge/models.py @@ -51,6 +51,10 @@ class TunnelConfig: reconnect: ReconnectPolicy = field(default_factory=ReconnectPolicy) health_check: Optional[HealthCheckConfig] = None direction: str = "reverse" # "reverse" (-R) or "local" (-L) + # Forward-destination host as seen from the remote end (direction "local") + # or from this workstation (direction "reverse"). Defaults to loopback; + # set e.g. a k3s ClusterIP to tunnel to an in-cluster Service. + remote_host: str = "127.0.0.1" cert_command: Optional[str] = None diff --git a/tests/test_manager.py b/tests/test_manager.py index 6d56b97..613617f 100644 --- a/tests/test_manager.py +++ b/tests/test_manager.py @@ -3,6 +3,8 @@ import os import signal from unittest.mock import MagicMock, patch +from dataclasses import replace + import pytest from bridge.models import BridgeState, ReconnectPolicy, TunnelConfig @@ -38,6 +40,16 @@ class TestBuildSshCommand: assert "-i" in cmd assert "ubuntu@host.local" in cmd + def test_remote_host_override_local(self, tunnel_cfg): + cfg = replace(tunnel_cfg, direction="local", remote_host="10.43.103.154") + cmd = build_ssh_command(cfg) + assert "-L" in cmd + assert f"{cfg.local_port}:10.43.103.154:{cfg.remote_port}" in cmd + + def test_remote_host_default_loopback(self, tunnel_cfg): + cmd = build_ssh_command(tunnel_cfg) + assert "18000:127.0.0.1:8000" in cmd + def test_server_alive_options(self, tunnel_cfg): cmd = build_ssh_command(tunnel_cfg) assert "-o" in cmd