Skip to content

Commit 167eaaf

Browse files
authored
feat: Add SSH jump host (-J) infrastructure and CLI integration (#30)
* feat: Add SSH jump host (-J) infrastructure and CLI integration This commit implements the foundation for SSH jump host support with OpenSSH-compatible -J syntax: - **Jump Host Parser**: Robust parsing of OpenSSH ProxyJump format (`user@host:port,user2@host2:port2`) - Supports single and multiple jump hosts - IPv6 address handling with bracket notation - Comprehensive input validation and error handling - **CLI Integration**: Full -J option support with jump host specification parsing - OpenSSH-compatible command-line syntax - Integration with existing command structure (exec, ping, upload, download) - Informative logging when jump hosts are detected - **Connection Management**: Infrastructure for jump host connection chains - JumpHostChain for managing multi-hop connections - Connection health monitoring and statistics - Error handling with jump context information - **SSH Client Extensions**: Enhanced tokio_client with jump host capabilities - Public session access for direct-tcpip channel operations - Infrastructure for channel-based SSH connections - ✅ Jump host specification parsing with comprehensive tests - ✅ CLI integration with -J option working - ✅ Connection chain management structure - ✅ All existing tests passing (99 tests) - 🚧 Actual SSH tunneling through jump hosts (requires deeper russh integration) - 17 new unit tests for jump host parsing and chain management - Comprehensive error handling tests for malformed specifications - IPv6 and edge case handling validated ```bash bssh -J jump@bastion.example.com -H target@internal.server "uptime" bssh -J "jump1@bastion1,jump2@bastion2" -C production "df -h" ``` The foundation is now in place for full jump host functionality. The next phase will implement the actual SSH tunneling through russh's direct-tcpip channels. feat: Complete SSH jump host (ProxyJump) implementation - Full SSH tunneling through jump hosts using russh direct-tcpip channels - OpenSSH ProxyJump syntax compatibility with -J/--jump-host option - Multi-hop connection chaining through intermediate jump hosts - Comprehensive authentication support (SSH agent, key files, passwords) - Connection timeout and error handling for all tunnel stages - CLI integration with existing cluster and single-host operations - Code refactoring to reduce function parameter counts via config structs - Documentation updates in README.md with usage examples Enables secure access to internal hosts through bastion servers with syntax like: bssh -J jump@bastion.example.com user@internal-host fix(security): Fix host key verification bypass and sensitive data handling - Priority: CRITICAL - Always verify host keys for intermediate jump hosts to prevent MITM attacks - Use Zeroizing wrapper for SSH key file contents to clear from memory - Pass strict mode configuration through the entire jump chain - Ensure all sensitive data (passwords, passphrases, keys) are properly zeroed fix(security): Add rate limiting for connection attempts - Priority: HIGH - Implement token bucket rate limiter to prevent DoS attacks - Default limits: 10 connection burst, 2 connections/second sustained - Per-host rate limiting with automatic cleanup of old buckets - Configurable rate limits via with_rate_limit() method - Apply rate limiting to all connection attempts (direct, jump hosts, destination) fix(perf): Fix connection pool resource leak - Priority: HIGH - Add automatic cleanup of stale connections (idle > 5 min, age > 30 min) - Clean up connections periodically when pool size exceeds threshold - Add connection age and idle time tracking - Implement proper Drop trait logging for debugging - Add methods to monitor active connection count fix(security): Add input sanitization for command execution - Priority: HIGH - Add comprehensive input sanitization module with validation functions - Sanitize commands to detect injection patterns and dangerous constructs - Validate and sanitize hostnames to prevent DNS/SSH injection - Validate and sanitize usernames with proper character restrictions - Apply sanitization to all command execution and jump host parsing - Add CommandValidationFailed error variant for proper error handling - Add comprehensive test coverage for sanitization functions fix: remove vendor files update: .gitignore * update: Cargo.lock * fix: handle IPv6 addresses in jump hosts and stabilize environment variable tests
1 parent f877d21 commit 167eaaf

20 files changed

Lines changed: 2636 additions & 139 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,4 @@ Thumbs.db
2424
.claude/
2525
.gemini/
2626
references/
27+
vendor/

Cargo.lock

Lines changed: 39 additions & 64 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ A high-performance SSH client with **SSH-compatible syntax** for both single-hos
1212
## Features
1313

1414
- **SSH Compatibility**: Drop-in replacement for SSH with compatible command-line syntax
15+
- **Jump Host Support**: Connect through bastion hosts using OpenSSH ProxyJump syntax (`-J`)
1516
- **Parallel Execution**: Execute commands across multiple nodes simultaneously
1617
- **Cluster Management**: Define and manage node clusters via configuration files
1718
- **Progress Tracking**: Real-time progress indicators for each node
@@ -98,6 +99,24 @@ bssh -o StrictHostKeyChecking=no user@host
9899
bssh -Q cipher
99100
```
100101

102+
### Jump Host Support (ProxyJump)
103+
```bash
104+
# Connect through a single jump host (bastion)
105+
bssh -J jump@bastion.example.com user@internal-server
106+
107+
# Multiple jump hosts (connection chain)
108+
bssh -J "jump1@proxy1,jump2@proxy2" user@final-destination
109+
110+
# Jump host with custom port
111+
bssh -J admin@bastion:2222 user@internal-host
112+
113+
# IPv6 jump host
114+
bssh -J "[2001:db8::1]:22" user@destination
115+
116+
# Combine with cluster operations
117+
bssh -J bastion.example.com -C production "uptime"
118+
```
119+
101120
### Multi-Server Mode (Cluster Operations)
102121
```bash
103122
# Using direct host specification

src/cli.rs

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,13 @@ pub struct Cli {
8282
)]
8383
pub password: bool,
8484

85+
#[arg(
86+
short = 'J',
87+
long = "jump-host",
88+
help = "Comma-separated list of jump hosts (ProxyJump)\nSpecify in [user@]hostname[:port] format, e.g.: 'jump1.example.com' or 'user@jump1:2222,jump2'\nSupports multiple hops for complex network topologies"
89+
)]
90+
pub jump_hosts: Option<String>,
91+
8592
#[arg(
8693
long = "parallel",
8794
default_value = "10",
@@ -160,14 +167,6 @@ pub struct Cli {
160167
)]
161168
pub no_tty: bool,
162169

163-
#[arg(
164-
short = 'J',
165-
long = "jump",
166-
value_name = "destination",
167-
help = "Connect via jump host(s) (ProxyJump)"
168-
)]
169-
pub jump_hosts: Option<String>,
170-
171170
#[arg(short = 'x', long = "no-x11", help = "Disable X11 forwarding")]
172171
pub no_x11: bool,
173172

src/commands/exec.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ pub struct ExecuteCommandParams<'a> {
3232
pub use_password: bool,
3333
pub output_dir: Option<&'a Path>,
3434
pub timeout: Option<u64>,
35+
pub jump_hosts: Option<&'a str>,
3536
}
3637

3738
pub async fn execute_command(params: ExecuteCommandParams<'_>) -> Result<()> {
@@ -49,7 +50,8 @@ pub async fn execute_command(params: ExecuteCommandParams<'_>) -> Result<()> {
4950
params.use_agent,
5051
params.use_password,
5152
)
52-
.with_timeout(params.timeout);
53+
.with_timeout(params.timeout)
54+
.with_jump_hosts(params.jump_hosts.map(|s| s.to_string()));
5355

5456
let results = executor.execute(params.command).await?;
5557

src/config.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -720,11 +720,22 @@ mod tests {
720720

721721
#[test]
722722
fn test_expand_tilde() {
723-
unsafe {
724-
std::env::set_var("HOME", "/home/user");
725-
}
723+
// Save original HOME value
724+
let original_home = std::env::var("HOME").ok();
725+
726+
// Set test HOME value
727+
std::env::set_var("HOME", "/home/user");
728+
726729
let path = Path::new("~/.ssh/config");
727730
let expanded = expand_tilde(path);
731+
732+
// Restore original HOME value
733+
if let Some(home) = original_home {
734+
std::env::set_var("HOME", home);
735+
} else {
736+
std::env::remove_var("HOME");
737+
}
738+
728739
assert_eq!(expanded, PathBuf::from("/home/user/.ssh/config"));
729740
}
730741

0 commit comments

Comments
 (0)