-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Problem Summary
There are two critical issues with the SSH jump host functionality:
- ProxyJump (
-J/--jump-host) only works for command execution, not for file transfers - Interactive mode times out when using jump hosts
These issues make jump host functionality unusable for many common SSH operations.
Issue 1: File Transfer Commands Don't Support Jump Hosts
Detailed Analysis
Location: src/executor.rs
The upload_to_node() (line 627-664) and download_from_node() (line 666-693) functions create SshClient instances but do not pass jump_hosts parameters:
// Line 627-664: upload_to_node
async fn upload_to_node(
node: Node,
local_path: &Path,
remote_path: &str,
key_path: Option<&str>,
strict_mode: StrictHostKeyChecking,
use_agent: bool,
use_password: bool,
) -> Result<()> {
let mut client = SshClient::new(node.host.clone(), node.port, node.username.clone());
let key_path = key_path.map(Path::new);
// ❌ NO jump_hosts parameter passed!
client
.upload_file(local_path, remote_path, key_path, Some(strict_mode), use_agent, use_password)
.await
}In contrast, execute_on_node_with_jump_hosts() (line 604-625) properly supports jump hosts:
async fn execute_on_node_with_jump_hosts(
node: Node,
command: &str,
config: &ExecutionConfig<'_>,
) -> Result<CommandResult> {
let connection_config = ConnectionConfig {
jump_hosts_spec: config.jump_hosts, // ✓ Jump hosts properly passed
// ...
};
client
.connect_and_execute_with_jump_hosts(command, &connection_config)
.await
}SshClient File Transfer Methods Lack Jump Host Support
Location: src/ssh/client.rs
upload_file()(line 44-173)download_file()(line 175-304)upload_dir()(line 306-423)download_dir()(line 425-540)
These methods only call connect_direct(), not connect_via_jump_hosts().
Expected Behavior
# Upload with jump host
bssh -J bastion.example.com -H target.internal upload local.txt /tmp/
# Download with jump host
bssh -J user@bastion:2222 -c production download /etc/config ./backups/
# Multi-hop jump hosts
bssh -J jump1,jump2,jump3 user@target upload app.tar.gz /opt/Current Behavior
- ✓ Command execution with
-Jworks correctly - ✗ File upload with
-Jfails (attempts direct connection) - ✗ File download with
-Jfails (attempts direct connection)
Issue 2: Interactive Mode Times Out with Jump Hosts
Problem Description
When using interactive mode with jump hosts, the connection times out and fails to establish. This affects both single-node SSH mode and multiplexed interactive sessions.
Location: src/main.rs (lines 417-479)
Reproduction Steps
# SSH mode interactive (like: ssh -J bastion user@target)
bssh -J bastion.example.com user@target.internal
# Interactive subcommand with jump host
bssh -J bastion.example.com -H target1,target2 interactive
# Multi-hop interactive
bssh -J jump1,jump2 user@targetExpected Behavior
- Connection should be established through the jump host chain
- Interactive shell should start immediately
- User input should be properly forwarded
- PTY allocation should work through jump hosts
- Session should remain stable without timeouts
Current Behavior
- Connection attempt starts
- Timeout occurs (likely at 30s connection timeout)
- No interactive shell is established
- Error message about connection timeout
Root Cause Analysis
Location: src/main.rs (lines 417-479)
The interactive mode initialization doesn't properly integrate jump hosts:
// Line 417-479: SSH mode interactive session
if cli.is_ssh_mode() && command.is_empty() {
// Interactive mode started, but jump_hosts not properly passed
let interactive_cmd = InteractiveCommand {
single_node: true,
multiplex: false,
// ...
key_path,
use_agent: cli.use_agent,
use_password: cli.password,
strict_mode,
pty_config,
use_pty,
};
// ❌ Missing: jump_hosts parameter in InteractiveCommand
}Location: src/commands/interactive.rs (check if InteractiveCommand struct has jump_hosts field)
The InteractiveCommand struct likely doesn't have:
jump_hostsfield- Logic to pass jump_hosts to SSH connection establishment
- Proper timeout handling for multi-hop connections
Potential Issues
-
Missing jump_hosts field in InteractiveCommand
- The struct doesn't store jump host specification
- Can't pass it to SSH client during connection
-
Connection timeout too short for multi-hop
- Default 30s timeout may be insufficient for 2+ hops
- Each hop needs time for: connection + auth + channel setup
- Suggested: 30s base + 15s per additional hop
-
PTY allocation through jump hosts
- PTY must be properly allocated through the final connection
- Channel forwarding needs to support interactive I/O
-
Error handling insufficient
- Timeout errors don't indicate which hop failed
- No retry logic for transient network issues
Implementation Analysis
Location: src/jump/chain.rs
The JumpHostChain::connect() method (line 229-278) exists and works for command execution. It should be used for interactive mode as well:
pub async fn connect(
&self,
destination_host: &str,
destination_port: u16,
destination_user: &str,
dest_auth_method: AuthMethod,
// ...
) -> Result<JumpConnection>This method returns a JumpConnection with a working Client that can be used for both command execution and interactive sessions.
Proposed Solution
Phase 1: File Transfer Support (Priority: High)
-
Modify
ExecutionConfiginsrc/executor.rs- Already has
jump_hosts: Option<&'a str>✓
- Already has
-
Update
upload_to_node()anddownload_from_node()async fn upload_to_node( node: Node, local_path: &Path, remote_path: &str, key_path: Option<&str>, strict_mode: StrictHostKeyChecking, use_agent: bool, use_password: bool, jump_hosts: Option<&str>, // ✓ Add this parameter ) -> Result<()>
-
Update
SshClientfile transfer methods- Add
jump_hosts_specto ConnectionConfig - Use existing
connect_via_jump_hosts()infrastructure
- Add
Phase 2: Interactive Mode Support (Priority: High)
-
Update InteractiveCommand struct
pub struct InteractiveCommand { // existing fields... pub jump_hosts: Option<String>, // ✓ Add this field }
-
Pass jump_hosts to SSH connection in interactive mode
- Use
JumpHostChain::connect()when jump hosts are specified - Handle PTY allocation through final connection
- Properly forward stdin/stdout through jump chain
- Use
-
Improve timeout handling
// Dynamic timeout based on hop count let base_timeout = Duration::from_secs(30); let per_hop_timeout = Duration::from_secs(15); let total_timeout = base_timeout + (per_hop_timeout * jump_host_count);
-
Better error messages
// Instead of: "Connection timeout" // Show: "Connection timeout at hop 2 of 3 (bastion2.example.com)"
Phase 3: SSH Config Integration (Priority: Medium)
-
Parse ProxyJump from SSH config
- Read
ProxyJumpdirective inSshConfig - Merge with CLI
-Joption (CLI takes precedence)
- Read
-
Auto-detect jump hosts from config
# ~/.ssh/config Host production-* ProxyJump bastion.company.com # This should work automatically: bssh production-web1 interactive
Testing Requirements
Unit Tests
- Jump host parameter propagation in file transfer
- InteractiveCommand with jump_hosts field
- Timeout calculation based on hop count
- Error message formatting with hop information
Integration Tests
-
File Transfer Tests:
- Upload single file through 1 jump host
- Download through 2 jump hosts
- Recursive upload/download through jump chain
-
Interactive Mode Tests:
- SSH mode interactive through 1 jump host
- Multi-node interactive through jump chain
- PTY allocation and terminal size detection
- Signal handling (Ctrl+C, Ctrl+D)
-
Timeout Tests:
- Verify connection succeeds within calculated timeout
- Verify timeout triggers at correct time
- Test error messages indicate which hop failed
Manual Testing Scenarios
# Scenario 1: File transfer through bastion
bssh -J bastion upload local.txt target:/tmp/
bssh -J bastion download target:/tmp/remote.txt ./
# Scenario 2: Interactive through multi-hop
bssh -J bastion1,bastion2 user@target
# Scenario 3: Combined operations
bssh -J bastion -H web1,web2 upload app.tar.gz /opt/
bssh -J bastion -H web1,web2 interactive
# Scenario 4: SSH config integration
# (with ProxyJump in ~/.ssh/config)
bssh production-server interactiveSecurity Considerations
- Authentication per hop: Each jump host requires proper authentication
- Host key verification: All hosts in chain must pass verification
- Connection cleanup: Ensure all hops properly closed on error
- Timeout limits: Prevent indefinite hanging connections
- Error messages: Don't leak internal network topology in errors
Implementation Architecture
The codebase already has complete jump host infrastructure:
- ✓
src/jump/parser.rs: ProxyJump syntax parsing (OpenSSH compatible) - ✓
src/jump/chain.rs: Multi-hop connection viaJumpHostChain - ✓
src/jump/connection.rs: Connection lifecycle management - ✓
src/ssh/client.rs:connect_via_jump_hosts()method exists
The infrastructure is complete, but integration is incomplete.
Priority
Critical Priority - Jump hosts are essential for:
- Enterprise environments with bastion hosts (security requirement)
- Compliance requirements (SOC2, PCI-DSS, HIPAA)
- Network-isolated environments
- Multi-region deployments
Interactive mode is a core SSH functionality that users expect to work seamlessly with jump hosts.
Related Files
src/executor.rs(lines 604-693) - File transfer functionssrc/ssh/client.rs(lines 44-540, 256-297) - SSH client methodssrc/main.rs(lines 183-207, 417-523) - Interactive mode initializationsrc/commands/interactive.rs- InteractiveCommand implementationsrc/jump/chain.rs- Jump host connection chain (complete)src/jump/parser.rs- Jump host parsing (complete)src/commands/exec.rs(line 197) - Working example
Additional Context
The working command execution implementation in execute_on_node_with_jump_hosts() provides a clear template for fixing both issues. The main work is:
- Propagating
jump_hostsparameter through function calls - Adding
jump_hostsfield toInteractiveCommand - Adjusting timeouts for multi-hop scenarios
- Testing and error handling improvements