Skip to content

Commit 6168f30

Browse files
committed
fix: Improve jump host authentication error messages and documentation
- Add debug logging for config jump_host resolution in dispatcher - Enhance authentication error messages to clearly indicate which host (jump host vs destination) failed and what method was attempted - Log auto-detected username when not specified in jump_host - Update --help for -J option with username behavior examples - Document user field vs jump_host user distinction in README.md - Add clear examples in example-config.yaml showing user field scope - Fix flaky tests by adding serial_test annotation
1 parent a6c7644 commit 6168f30

File tree

7 files changed

+235
-45
lines changed

7 files changed

+235
-45
lines changed

README.md

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -779,6 +779,24 @@ clusters:
779779
- Environment variables (`${VAR}` or `$VAR`) are expanded in jump_host values
780780
- Node-level jump_host requires detailed node syntax (not simple hostname strings)
781781

782+
**Important: Username Handling**
783+
784+
The `user` field in config applies only to **destination nodes**, not to jump hosts:
785+
786+
```yaml
787+
clusters:
788+
internal:
789+
nodes:
790+
- 192.168.0.100
791+
user: admin # Used for destination (192.168.0.100)
792+
jump_host: bai@bastion # User 'bai' for bastion (jump host)
793+
```
794+
795+
- **With username in jump_host:** `bai@bastion:4300` → uses "bai" for bastion
796+
- **Without username:** `bastion:4300` → uses your current local username (like OpenSSH)
797+
798+
If jump host authentication fails due to username mismatch, specify the username explicitly in the `jump_host` field.
799+
782800
## SSH Configuration Support
783801

784802
bssh fully supports OpenSSH-compatible configuration files via the `-F` flag or default SSH config locations (`~/.ssh/config`, `/etc/ssh/ssh_config`). In addition to standard SSH directives, bssh supports advanced options for certificate-based authentication and port forwarding control.
@@ -1135,7 +1153,7 @@ Options:
11351153
-A, --use-agent Use SSH agent for authentication (Unix/Linux/macOS only)
11361154
-P, --password Use password authentication (will prompt for password)
11371155
-S, --sudo-password Prompt for sudo password to auto-respond to sudo prompts
1138-
-J, --jump-host <JUMP_HOSTS> Comma-separated list of jump hosts (ProxyJump)
1156+
-J, --jump-host <JUMP_HOSTS> Jump hosts: [user@]host[:port],... (uses local user if not specified)
11391157
-L, --local-forward <SPEC> Local port forwarding [bind_address:]port:host:hostport
11401158
-R, --remote-forward <SPEC> Remote port forwarding [bind_address:]port:host:hostport
11411159
-D, --dynamic-forward <SPEC> Dynamic port forwarding (SOCKS) [bind_address:]port[/version]

example-config.yaml

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,14 @@
11
# Example configuration for bssh
2+
#
3+
# USER FIELD BEHAVIOR:
4+
# - 'user' in defaults/cluster applies to DESTINATION nodes only
5+
# - For jump hosts, specify username in jump_host string: user@jumphost:port
6+
# - If no username in jump_host, your current local username ($USER) is used
7+
#
8+
# Example:
9+
# user: admin # Used for destination nodes
10+
# jump_host: bai@bastion # User 'bai' for bastion, 'admin' for destination
11+
#
212
defaults:
313
user: ubuntu
414
port: 22
@@ -38,13 +48,17 @@ clusters:
3848
# jump_host: prod-bastion.example.com
3949

4050
# Example: Cluster behind a jump host/bastion
51+
# IMPORTANT: 'user' field applies to DESTINATION nodes only.
52+
# For jump host authentication, specify username in jump_host string: user@host:port
53+
# If no username is specified in jump_host, your current local username is used.
4154
internal:
4255
nodes:
4356
- host: internal1.private
4457
- host: internal2.private
4558
- host: internal3.private
46-
user: admin
47-
jump_host: bastion.example.com # All nodes accessible via bastion
59+
user: admin # User for internal*.private (destination nodes)
60+
jump_host: [email protected] # User 'jumpuser' for bastion (jump host)
61+
# Alternative: jump_host: bastion.example.com # Uses your local username for bastion
4862

4963
# Example: Mixed direct and jump host access
5064
hybrid:

src/app/dispatcher.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -416,10 +416,22 @@ async fn handle_exec_command(cli: &Cli, ctx: &AppContext, command: &str) -> Resu
416416
};
417417

418418
// Resolve jump_hosts: CLI takes precedence, then config
419-
let jump_hosts = cli.jump_hosts.clone().or_else(|| {
420-
ctx.config
421-
.get_cluster_jump_host(ctx.cluster_name.as_deref().or(cli.cluster.as_deref()))
422-
});
419+
let effective_cluster_name = ctx.cluster_name.as_deref().or(cli.cluster.as_deref());
420+
let config_jump_host = ctx.config.get_cluster_jump_host(effective_cluster_name);
421+
let jump_hosts = cli.jump_hosts.clone().or(config_jump_host.clone());
422+
423+
// Debug logging for jump host resolution
424+
tracing::debug!(
425+
"Jump host resolution: cli={:?}, config={:?}, effective={:?}, cluster={:?}",
426+
cli.jump_hosts,
427+
config_jump_host,
428+
jump_hosts,
429+
effective_cluster_name
430+
);
431+
432+
if let Some(ref jh) = jump_hosts {
433+
tracing::info!("Using jump host: {}", jh);
434+
}
423435

424436
let params = ExecuteCommandParams {
425437
nodes: ctx.nodes.clone(),

src/cli/bssh.rs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,14 @@ pub struct Cli {
114114
#[arg(
115115
short = 'J',
116116
long = "jump-host",
117-
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"
117+
help = "Comma-separated list of jump hosts (ProxyJump)\n\
118+
Format: [user@]hostname[:port]\n\
119+
Examples:\n \
120+
bai@bastion:4300 (user 'bai' on port 4300)\n \
121+
bastion.example.com (current local user, port 22)\n \
122+
user@hop1:22,admin@hop2 (multi-hop chain)\n\
123+
Note: If no username specified, your current local username is used.\n\
124+
This is separate from -u/--user which sets the destination server user."
118125
)]
119126
pub jump_hosts: Option<String>,
120127

src/jump/chain.rs

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,24 @@ impl JumpHostChain {
342342
.await
343343
.with_context(|| format!("Rate limited for jump host {}", jump_host.host))?;
344344

345+
// Log the effective username being used, especially helpful when auto-detected
346+
let effective_user = jump_host.effective_user();
347+
if jump_host.user.is_none() {
348+
tracing::info!(
349+
"Connecting to jump host {}:{} as user '{}' (auto-detected from current user)",
350+
jump_host.host,
351+
jump_host.effective_port(),
352+
effective_user
353+
);
354+
} else {
355+
tracing::info!(
356+
"Connecting to jump host {}:{} as user '{}'",
357+
jump_host.host,
358+
jump_host.effective_port(),
359+
effective_user
360+
);
361+
}
362+
345363
let auth_method = auth::determine_auth_method(
346364
jump_host,
347365
key_path,
@@ -356,7 +374,7 @@ impl JumpHostChain {
356374
self.connect_timeout,
357375
crate::ssh::tokio_client::Client::connect(
358376
(jump_host.host.as_str(), jump_host.effective_port()),
359-
&jump_host.effective_user(),
377+
&effective_user,
360378
auth_method,
361379
check_method,
362380
),

0 commit comments

Comments
 (0)