Skip to content

swarupsengupta2007/go-ssh-jumphost

Repository files navigation

go-ssh-jumphost

A lightweight ProxyCommand utility that implements OpenSSH's -J (jump host) behavior for SSH clients that lack native support for it, such as WinSCP, PuTTY, or any tool that accepts a ProxyCommand string.

It connects through one or more SSH jump hosts in sequence and bridges the final TCP stream to standard input and output, ensuring the parent SSH client sees a direct connection to the destination.

The Need for This Project

Many popular SSH graphical clients and utilities do not natively support complex multi-hop SSH routing or the -J flag introduced in newer OpenSSH versions. Passing a ProxyCommand string is the standard workaround, but configuring a sequence of chained commands manually is error-prone and brittle.

This project exists to act as a robust, single-binary ProxyCommand drop-in replacement. It parses standard OpenSSH -J syntax, automatically manages the SSH handshake across all intermediaries, dynamically establishes the required TCP tunnels, and natively handles Multi-Factor Authentication (such as TOTP) at any point in the jump chain without interfering with the parent application's stream.

Installation

Via go install:

go install github.com/swarupsengupta2007/go-ssh-jumphost@latest

Download a pre-built binary from the Releases page.

Command-Line Options

The utility supports several command-line arguments to control its behavior:

  • -J <hosts>

    • Status: Required
    • Format: Comma-separated list of jump hosts (user@host:port[,user@host:port,...]).
    • Description: Defines the exact sequence of intermediate jump hosts to traverse.
  • -timeout <duration>

    • Status: Optional
    • Default: 30s
    • Description: Specifies the dial and SSH handshake timeout per individual SSH hop. Set to 0 to disable the timeout completely.
  • -strict-host-key-checking

    • Status: Optional
    • Default: false
    • Description: Enforces strict host key verification. Verifies the host key of each connection against the user's ~/.ssh/known_hosts file or the file specified via the -hosts-file flag.
  • -no-ssh-config

    • Status: Optional
    • Default: false
    • Description: Prevents the utility from loading the OpenSSH configuration file (~/.ssh/config).
  • -ssh-config <file>

    • Status: Optional
    • Description: Explicitly loads a specific SSH configuration file instead of the user's default ~/.ssh/config. Cannot be used concurrently with -no-ssh-config.
  • -hosts-file <file>

    • Status: Optional
    • Description: Explicitly loads a specific known_hosts file, bypassing the default ~/.ssh/known_hosts location and overriding any UserKnownHostsFile directive discovered in the SSH configuration file.
  • -I

    • Status: Optional
    • Default: false
    • Description: Forces a GUI input dialog for interactive prompts (like passwords or TOTP). This is specific to Windows and falls back to the standard console interface on other platforms.
  • -v

    • Status: Optional
    • Default: false
    • Description: Enables verbose logging. All debug and operational logs are strictly written to stderr to avoid corrupting the stdout stream used by the tunneling protocol.

Positional Argument: After all flags, the utility expects a single positional argument representing the final destination host in the format [user@]destination[:port].

Interactions with SSH Configuration

Unless the -no-ssh-config flag is passed, go-ssh-jumphost automatically loads the user's ~/.ssh/config file. It applies the discovered configurations for every host involved in the jump chain as well as the final destination.

Supported Options

The following SSH configuration directives are parsed and actively applied by the utility:

  • HostName: Resolves an alias to a fully qualified domain name or IP address.
  • User: Specifies the login username.
  • Port: Specifies the port number to connect to.
  • IdentityFile: Specifies a file from which the user's identifying private key is read.
  • UserKnownHostsFile: Specifies a custom file for host key verification.
  • StrictHostKeyChecking: Dictates whether host keys should be strictly verified. Values of yes or ask enable strict checking; all other values disable it.
  • ProxyJump: Specifies a list of intermediate jump hosts implicitly chained before the defined host.

Ignored Options

Directives that go-ssh-jumphost recognises but does not implement (e.g., LogLevel, ForwardAgent, Ciphers, Compression, RequestTTY) are strictly ignored. The application executes normally without acting on them.

Precedence of Options

When determining the final connection parameters, go-ssh-jumphost adheres to the following strict order of precedence:

  1. Command-Line Arguments: Options explicitly provided via the command-line flags or explicit jump host syntax (e.g., specifying user@host:port directly) take absolute priority.
  2. SSH Configuration File: If a parameter is not defined on the command-line, the utility falls back to the configurations defined in ~/.ssh/config.

Values explicitly set on the command line override similar options in the SSH configuration file. For instance, the --hosts-file flag supersedes the UserKnownHostsFile directive.

Jump Host Chaining via ProxyJump

The utility natively handles automatic jump host chaining if it is implicitly derived from the SSH configuration file.

If a host listed in the -J argument or the final destination contains a ProxyJump directive in the SSH configuration, its respective jump hosts are automatically expanded and prepended to the host sequence.

This expansion evaluates recursively. It comprehensively handles wildcard patterns on resolved IP addresses identically to OpenSSH behavior with CanonicalizeHostname yes. A ProxyJump none configuration value explicitly suppresses any further expansion.

Built-In Autodetection Features

The project includes intelligent autodetection mechanisms out of the box to streamline the user experience:

  • GUI Dialog Autodetection (Windows): Running inside graphical parent applications (like WinSCP or plink) often detaches the standard console. The utility inspects the parent executable name. If invoked by known headless clients, it automatically enforces the -I flag behavior, spawning a native Win32 GUI dialog for necessary input (e.g., password or TOTP) to prevent silent hangs without requiring configuration changes.
  • Terminal Input Management: The utility utilizes raw-mode terminal reading across operating systems to ensure secure password input. On Unix systems, it securely interacts with /dev/tty. On Windows, it binds to the CONIN$ and CONOUT$ buffers to safely read input and display prompts, natively bypassing the piped streams used by the ProxyCommand framework.

Host Key Verification

The utility uses InsecureIgnoreHostKey by default. This is highly suitable for standard ProxyCommand usage, as the outer SSH application performs its own primary host key verification.

To enable verification natively at the intermediate jump-host layer, users must pass the -strict-host-key-checking flag. The project will verify against ~/.ssh/known_hosts, utilize paths defined in UserKnownHostsFile, or leverage the explicit path supplied via -hosts-file.

You can populate the system's known hosts explicitly using ssh-keyscan:

ssh-keyscan -H gw1.example.com >> ~/.ssh/known_hosts
ssh-keyscan -H gw2.internal >> ~/.ssh/known_hosts

Authentication Execution

The utility attempts authentication methods in the following strict order for all established connections in the chain:

  1. SSH Agent: Attempted directly via SSH_AUTH_SOCK on Unix environments or the OpenSSH named pipe on Windows.
  2. Key Files: Used as a fallback if the SSH agent is unavailable. The utility will iterate through standard identity files (~/.ssh/id_rsa, ~/.ssh/id_ecdsa, ~/.ssh/id_ed25519) followed by any extra paths parsed from IdentityFile configuration directives.
  3. Keyboard-Interactive: Always appended and executed last after public key methods, providing full support for Password and Time-Based One-Time Passwords (TOTP) scenarios.

Troubleshooting

In the event of a timeout, failure, or unexpected jump host resolution, append the -v (verbose) flag to your execution command.

Executing with -v activates granular logging isolated entirely to the stderr stream. This logging outputs the full lifecycle of the process:

  • Verification of parsed command-line variables.
  • Discoveries of configurations, including cryptographic hashes of the evaluated SSH config file.
  • Specific evaluations of resolved ProxyJump concatenations.
  • Explicit [WARN] entries detailing any ignored, unsupported SSH configuration keys mapped to specific hosts.
  • Detailed network dial and TCP tunneling lifecycle markers for every individual connection hop.

This output instantly exposes whether configuration overrides were successful or if specific timeouts caused the failure path.

Usage Examples

OpenSSH ProxyCommand

To specify in ~/.ssh/config:

Host myserver
    HostName 10.0.0.100
    User alice
    ProxyCommand go-ssh-jumphost -J [email protected] alice@%h:%p

For a multi-hop scenario:

Host deepserver
    HostName 10.0.0.200
    User alice
    ProxyCommand go-ssh-jumphost -J [email protected],[email protected] alice@%h:%p

WinSCP

  1. Access Session Advanced Connection Tunnel.
  2. Set Tunnel type to Local.
  3. In the ProxyCommand field, enter:
go-ssh-jumphost.exe -J [email protected]:22 alice@%host:%port

Replace alice, gw1.example.com, and ports to match your operational environment.

Building from Source

# Compile development build for the host operating system architecture
make build

# Compile all release binaries to the dist directory
make dist

# Execute unit tests
make test

# Execute static analysis
make vet

Release binaries are generated inside the dist directory and are named by target operating system and architecture. Note that within the Go toolchain, 64-bit ARMv8 (GOARCH=arm64) aligns with the exact linux-arm64 binaries. The linux-armv7 binaries strictly target 32-bit ARMv7 configurations.

Integration Tests

Executing the integration suites requires an active Docker and Docker Compose environment:

# Start the test SSH stack
make integration-up

# Execute the test suite
bash test/integration_test.sh

# Terminate and clean up the test environment
make integration-down

Smoke Test

./go-ssh-jumphost -v -timeout 2s -J [email protected] destination

This should terminate in approximately two seconds with a clear dial error, preventing the program from hanging indefinitely.

Made with the help of Claude-Code (Sonnet 4.6), Antigravity (Gemini 3.1 Pro) and Github Copilot.

About

A proxy command for mimicking -J switch of ssh, for use with any application that supports a ProxyCommand

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors