Skip to content

Commit 99979e7

Browse files
authored
fix: collect --password once up-front and honor -S consistently across subcommands (#201)
* fix: collect --password once up-front and honor -S across subcommands Fixes two defects in credential collection that surfaced together in `bssh -C orin -l lablup --password -S ping` (issue #200). Defect 1 — `--password` is collected lazily per-connection. The previous implementation prompted via `rpassword::prompt_password()` inside each per-node authentication task (`ssh/auth.rs::password_auth()`). The executor fans out N parallel connection attempts, so multiple tasks raced for stdin: the prompt could be missed, repeated per node, or interleaved with the indicatif progress UI rendering. The `-S` (sudo-password) path already did this correctly — collect once in the dispatcher, wrap in `Arc<SudoPassword>`, share across tasks — and now `--password` follows the same pattern. Defect 2 — `-S` was silently dropped by `ping`, `upload`, and `download`. The dispatcher only read `cli.sudo_password` in the `exec` and interactive branches. Users could pass `-S` to `ping`/`upload`/`download` without any error, but the flag had no effect. Changes: - Add `src/security/password.rs` mirroring the `SudoPassword` design: a `Password` wrapper backed by `secrecy::SecretString` (auto-zeroized on drop), `prompt_password()` with a non-host-specific prompt ("Enter SSH password (used for all hosts): "), `get_password_from_env()` reading `BSSH_PASSWORD`, and `get_password(warn_env)` combining both. Wrapped in `Arc<Password>` for sharing across per-node tasks without duplicating secret material. - Hoist `--password` collection to `dispatch_command` in `src/app/dispatcher.rs`. Prompt runs once, before any `ParallelExecutor` is built and before any indicatif `MultiProgress` is initialized, so the terminal is in a clean state when reading the password. The resulting `Arc<Password>` is threaded through `ping_nodes`, `FileTransferParams` (upload/download), `ExecuteCommandParams`, `InteractiveCommand`, and the SSH-mode interactive path. - Extend `AuthContext` in `src/ssh/auth.rs` with a `password: Option<Arc<Password>>` field and a `with_pre_collected_password()` builder. `password_auth()` now consumes the pre-collected value when available; the in-task `rpassword::prompt_password()` call remains only as the fallback for the OpenSSH-style "all key methods failed, prompt for password" path (which has no dispatcher-side collection because it is opportunistic). - Thread `ssh_password: Option<Arc<Password>>` through the executor (`ParallelExecutor::with_ssh_password`), `ExecutionConfig`, `ConnectionConfig`, and the SFTP `*_with_jump_hosts` helpers. Every per-node task clones the `Arc` so all tasks observe the same secret without copying it. - For `-S`: emit `tracing::warn!` in the dispatcher when the flag is passed to a subcommand where it has no effect (`ping`, `upload`, `download`, `list`, `cache-stats`). Chose a warning over a hard reject to match typical CLI UX — existing scripts that happen to pass `-S` to non-applicable subcommands keep working but the user gets visible feedback. `exec` and interactive paths continue to honor `-S` as before. Tests: - 9 unit tests in `src/security/password.rs` mirror the structure of `src/security/sudo.rs`: creation, empty rejection, debug redaction, clone independence, `Arc` sharing, env var handling (empty/valid/unset), and a dispatcher-collection-pattern test that verifies a single `get_password()` call yields an `Arc<Password>` observably shared across three simulated per-node tasks. - 2 new tests in `src/ssh/auth.rs`: `test_auth_context_with_pre_collected_password` checks the builder and Arc sharing semantics; `test_password_auth_uses_pre_collected_password` verifies `password_auth()` returns immediately without prompting when the pre-collected value is set (critical: it never blocks on stdin in non-interactive test environments). - All existing tests (1233 lib tests + integration suites) continue to pass. `cargo build --release`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo fmt --check` all pass. Closes #200 * fix: thread pre-collected password through jump-host and legacy command paths Addresses pr-reviewer findings on PR #201: - jump::chain::auth::determine_auth_method now consumes Arc<Password> instead of prompting per-call (issue #200 C1) - ssh::client::SshClient::connect_and_execute_with_host_check accepts Arc<Password> so the download glob-resolution step reuses the dispatcher's single prompt (issue #200 C2) - commands::exec::execute_command_with_forwarding now builds AuthMethod::with_password from the pre-collected secret instead of erroring after the user has already entered a password (issue #200 M1) * fix: route -S ignored warning to stderr `tracing::warn!` lands on stdout under the default tracing subscriber, so the `-S has no effect` warning was being mixed into stdout — breaking `bssh ... ping | grep ...` style pipelines and contradicting the PR description's contract. Switch to `eprintln!` to match the existing pattern used for the `BSSH_PASSWORD` env warning in `src/security/password.rs:168-171`. * docs: document BSSH_PASSWORD env var and single-prompt behavior Add CHANGELOG entry for #201 (collect --password once up-front, -S warning to stderr). Document the new BSSH_PASSWORD environment variable in the README environment-variables section and in the bssh.1 man page. Update the --password flag description in the man page to describe the single-prompt / Arc-shared behavior introduced by #201.
1 parent 72169fe commit 99979e7

28 files changed

Lines changed: 765 additions & 49 deletions

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [2.1.5] - Unreleased
11+
12+
### Fixed
13+
- **`--password` prompt is now collected once up-front and shared across all parallel connection tasks** (#201, closes #200). Previously each per-node SSH task prompted for the password independently, racing each other for stdin and interleaving with the progress UI. The dispatcher now collects the password before any executor or `indicatif` UI is initialized and threads an `Arc<Password>` to every per-node auth task — matching the existing `-S` (`SudoPassword`) pattern. The `BSSH_PASSWORD` environment variable is supported for automation scenarios (discouraged; see security notes below).
14+
- **`-S` / `--sudo-password` warning now routes to stderr** when passed to subcommands where it has no effect (`ping`, `upload`, `download`, `list`), keeping stdout clean for scripts that consume bssh output (#201).
15+
1016
## [2.1.4] - 2026-05-10
1117

1218
### Performance

README.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -574,8 +574,10 @@ bssh supports multiple authentication methods:
574574
- **Explicit**: Use `-A` flag to force SSH agent authentication
575575

576576
### Password Authentication
577-
- Use `-P` flag to enable password authentication
577+
- Use `-P` / `--password` flag to enable password authentication
578+
- The password is prompted **once up-front**, before any parallel connection tasks start, and is shared securely across all nodes — the prompt appears exactly once regardless of how many hosts are targeted
578579
- Password is prompted securely without echo
580+
- For automation, set `BSSH_PASSWORD` in the environment (not recommended; see security notes in the Sudo Password section)
579581

580582
### Examples
581583
```bash
@@ -656,6 +658,15 @@ bssh supports configuration via environment variables:
656658

657659
- **`SSH_AUTH_SOCK`**: SSH agent socket path (Unix-like systems)
658660

661+
### SSH Password Variable
662+
663+
- **`BSSH_PASSWORD`**: SSH password for automated password authentication
664+
- Used when `--password` / `-P` is set and `BSSH_PASSWORD` is non-empty; skips the interactive prompt
665+
- **WARNING**: Not recommended for security reasons
666+
- Environment variables may be visible in process listings and shell history
667+
- Use the interactive `-P` prompt instead for security-sensitive operations
668+
- Example: `BSSH_PASSWORD=secret bssh -P -H "user@host" "uptime"`
669+
659670
### Sudo Password Variable
660671

661672
- **`BSSH_SUDO_PASSWORD`**: Sudo password for automated sudo authentication

docs/man/bssh.1

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,14 @@ is not available or authentication fails.
155155

156156
.TP
157157
.BR \-\-password
158-
Use password authentication. When this option is specified, bssh will
159-
prompt for the password securely without echoing it to the terminal.
158+
Use password authentication. The password is collected once up-front,
159+
before any parallel connection tasks are started, and is shared securely
160+
across all target nodes. The prompt appears exactly once regardless of
161+
how many hosts are targeted. If the
162+
.B BSSH_PASSWORD
163+
environment variable is set and non-empty, it is used instead of
164+
prompting interactively (not recommended; see
165+
.BR ENVIRONMENT ).
160166
This is useful for systems that don't have SSH keys configured.
161167

162168
.TP
@@ -1796,6 +1802,23 @@ attacks while allowing flexible jump host configurations.
17961802
.br
17971803
Example: BSSH_MAX_JUMP_HOSTS=20 bssh -J host1,host2,...,host20 target
17981804

1805+
.TP
1806+
.B BSSH_PASSWORD
1807+
SSH password for automated password authentication. When set along with the
1808+
.B \-\-password
1809+
flag and the variable is non-empty, bssh uses this value instead of
1810+
prompting interactively. The password is still collected once up-front and
1811+
shared across all parallel connection tasks.
1812+
.br
1813+
.B WARNING:
1814+
Using environment variables for passwords is not recommended for production
1815+
use as they may be visible in process listings, shell history, or logs.
1816+
Prefer the interactive
1817+
.B \-\-password
1818+
prompt for security-sensitive operations.
1819+
.br
1820+
Example: BSSH_PASSWORD=mysecret bssh --password -H "user@host" "uptime"
1821+
17991822
.TP
18001823
.B BSSH_SUDO_PASSWORD
18011824
Sudo password for automated sudo authentication. When set along with the

examples/interactive_demo.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ async fn main() -> anyhow::Result<()> {
5454
key_path: None,
5555
use_agent: false,
5656
use_password: false,
57+
ssh_password: None,
5758
#[cfg(target_os = "macos")]
5859
use_keychain: false,
5960
strict_mode: StrictHostKeyChecking::AcceptNew,

src/app/dispatcher.rs

Lines changed: 79 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use bssh::{
2727
},
2828
config::InteractiveMode,
2929
pty::PtyConfig,
30-
security::get_sudo_password,
30+
security::{Password, get_password, get_sudo_password},
3131
ssh::tokio_client::{DEFAULT_KEEPALIVE_INTERVAL, DEFAULT_KEEPALIVE_MAX, SshConnectionConfig},
3232
};
3333
use std::path::{Path, PathBuf};
@@ -83,6 +83,38 @@ fn build_ssh_connection_config(
8383
ssh_connection_config
8484
}
8585

86+
/// Decide whether `-S` (sudo-password) is meaningful for the given subcommand.
87+
///
88+
/// `exec` and the interactive paths legitimately need a sudo password; the
89+
/// other subcommands run either no command (`ping` just runs `true`) or operate
90+
/// over SFTP (`upload`, `download`, `list`), so sudo is never relevant.
91+
fn sudo_password_is_applicable(command: &Option<Commands>) -> bool {
92+
match command {
93+
Some(Commands::Ping)
94+
| Some(Commands::Upload { .. })
95+
| Some(Commands::Download { .. })
96+
| Some(Commands::List)
97+
| Some(Commands::CacheStats { .. }) => false,
98+
// exec (None), Interactive, and unknown future subcommands default to
99+
// "applicable" — better to over-accept than silently strip a flag the
100+
// user explicitly passed.
101+
_ => true,
102+
}
103+
}
104+
105+
/// Human-readable name of the currently-dispatched subcommand for diagnostics.
106+
fn subcommand_name(command: &Option<Commands>) -> &'static str {
107+
match command {
108+
Some(Commands::List) => "list",
109+
Some(Commands::Ping) => "ping",
110+
Some(Commands::Upload { .. }) => "upload",
111+
Some(Commands::Download { .. }) => "download",
112+
Some(Commands::Interactive { .. }) => "interactive",
113+
Some(Commands::CacheStats { .. }) => "cache-stats",
114+
None => "exec",
115+
}
116+
}
117+
86118
/// Dispatch commands to their appropriate handlers
87119
pub async fn dispatch_command(cli: &Cli, ctx: &AppContext) -> Result<()> {
88120
// Get command to execute
@@ -100,6 +132,36 @@ pub async fn dispatch_command(cli: &Cli, ctx: &AppContext) -> Result<()> {
100132
);
101133
}
102134

135+
// Warn if -S is passed to subcommands where it has no effect. We choose
136+
// a warning (not a hard reject) to match typical CLI UX: existing scripts
137+
// that happen to pass -S to `ping` or `upload` keep working, but the user
138+
// gets visible feedback that the flag is being ignored.
139+
if cli.sudo_password && !sudo_password_is_applicable(&cli.command) {
140+
eprintln!(
141+
"Warning: --sudo-password (-S) has no effect for the `{}` subcommand and will be ignored",
142+
subcommand_name(&cli.command)
143+
);
144+
}
145+
146+
// Hoist `--password` collection: prompt ONCE here, before any per-node
147+
// SSH connection task is spawned and before the indicatif progress UI is
148+
// rendered. Previously this prompt was issued inside each parallel auth
149+
// task (see `ssh/auth.rs::password_auth()`), which interleaved with the
150+
// progress bar and could fan out N concurrent stdin reads. Collecting
151+
// here mirrors what `-S` (`SudoPassword`) does and is the entire point
152+
// of issue #200.
153+
//
154+
// The returned value is wrapped in `Arc<Password>` so cloning across
155+
// per-node tasks does not duplicate the underlying secret; on drop, the
156+
// memory is zeroized by `secrecy::SecretString`.
157+
let ssh_password: Option<Arc<Password>> = if cli.password {
158+
Some(Arc::new(get_password(true).map_err(|e| {
159+
anyhow::anyhow!("Failed to collect SSH password: {e}")
160+
})?))
161+
} else {
162+
None
163+
};
164+
103165
// Calculate hostname for SSH config integration
104166
let hostname_for_ssh_config = if cli.is_ssh_mode() {
105167
cli.parse_destination().map(|(_, host, _)| host)
@@ -143,6 +205,7 @@ pub async fn dispatch_command(cli: &Cli, ctx: &AppContext) -> Result<()> {
143205
cli.timeout,
144206
Some(cli.connect_timeout),
145207
jump_hosts,
208+
ssh_password.clone(),
146209
)
147210
.await
148211
}
@@ -172,6 +235,7 @@ pub async fn dispatch_command(cli: &Cli, ctx: &AppContext) -> Result<()> {
172235
strict_mode: ctx.strict_mode,
173236
use_agent: cli.use_agent,
174237
use_password: cli.password,
238+
ssh_password: ssh_password.clone(),
175239
recursive: *recursive,
176240
ssh_config: Some(&ctx.ssh_config),
177241
jump_hosts,
@@ -204,6 +268,7 @@ pub async fn dispatch_command(cli: &Cli, ctx: &AppContext) -> Result<()> {
204268
strict_mode: ctx.strict_mode,
205269
use_agent: cli.use_agent,
206270
use_password: cli.password,
271+
ssh_password: ssh_password.clone(),
207272
recursive: *recursive,
208273
ssh_config: Some(&ctx.ssh_config),
209274
jump_hosts,
@@ -225,6 +290,7 @@ pub async fn dispatch_command(cli: &Cli, ctx: &AppContext) -> Result<()> {
225290
prompt_format,
226291
history_file,
227292
work_dir.as_deref(),
293+
ssh_password.clone(),
228294
)
229295
.await
230296
}
@@ -234,12 +300,13 @@ pub async fn dispatch_command(cli: &Cli, ctx: &AppContext) -> Result<()> {
234300
}
235301
None => {
236302
// Execute command (auto-exec or interactive shell)
237-
handle_exec_command(cli, ctx, &command).await
303+
handle_exec_command(cli, ctx, &command, ssh_password.clone()).await
238304
}
239305
}
240306
}
241307

242308
/// Handle interactive command execution
309+
#[allow(clippy::too_many_arguments)]
243310
async fn handle_interactive_command(
244311
cli: &Cli,
245312
ctx: &AppContext,
@@ -248,6 +315,7 @@ async fn handle_interactive_command(
248315
prompt_format: &str,
249316
history_file: &Path,
250317
work_dir: Option<&str>,
318+
ssh_password: Option<Arc<Password>>,
251319
) -> Result<()> {
252320
// Get interactive config from configuration file (with cluster-specific overrides)
253321
let cluster_name = cli.cluster.as_deref();
@@ -340,6 +408,7 @@ async fn handle_interactive_command(
340408
key_path,
341409
use_agent: cli.use_agent,
342410
use_password: cli.password,
411+
ssh_password,
343412
#[cfg(target_os = "macos")]
344413
use_keychain,
345414
strict_mode: ctx.strict_mode,
@@ -358,7 +427,12 @@ async fn handle_interactive_command(
358427
}
359428

360429
/// Handle exec command or SSH mode interactive session
361-
async fn handle_exec_command(cli: &Cli, ctx: &AppContext, command: &str) -> Result<()> {
430+
async fn handle_exec_command(
431+
cli: &Cli,
432+
ctx: &AppContext,
433+
command: &str,
434+
ssh_password: Option<Arc<Password>>,
435+
) -> Result<()> {
362436
// In SSH mode without command, start interactive session
363437
if cli.is_ssh_mode() && command.is_empty() {
364438
// SSH mode interactive session (like ssh user@host)
@@ -414,6 +488,7 @@ async fn handle_exec_command(cli: &Cli, ctx: &AppContext, command: &str) -> Resu
414488
key_path,
415489
use_agent: cli.use_agent,
416490
use_password: cli.password,
491+
ssh_password,
417492
#[cfg(target_os = "macos")]
418493
use_keychain,
419494
strict_mode: ctx.strict_mode,
@@ -504,6 +579,7 @@ async fn handle_exec_command(cli: &Cli, ctx: &AppContext, command: &str) -> Resu
504579
strict_mode: ctx.strict_mode,
505580
use_agent: cli.use_agent,
506581
use_password: cli.password,
582+
ssh_password,
507583
#[cfg(target_os = "macos")]
508584
use_keychain,
509585
output_dir: cli.output_dir.as_deref(),

src/commands/download.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ pub async fn download_file(
5757
params.use_agent,
5858
params.use_password,
5959
)
60-
.with_jump_hosts(params.jump_hosts.clone());
60+
.with_jump_hosts(params.jump_hosts.clone())
61+
.with_ssh_password(params.ssh_password.clone());
6162
if let Some(ssh_config) = params.ssh_config {
6263
executor = executor.with_ssh_config(Some(ssh_config.clone()));
6364
}
@@ -121,6 +122,7 @@ pub async fn download_file(
121122
params.jump_hosts.as_deref(), // Pass jump hosts from params
122123
None, // Use default connect timeout
123124
params.ssh_config, // Pass ssh_config for ProxyJump resolution
125+
params.ssh_password.clone(),
124126
)
125127
.await;
126128

@@ -185,6 +187,10 @@ pub async fn download_file(
185187
params.use_agent,
186188
params.use_password,
187189
None, // Use default timeout for ls command
190+
// Issue #200 (C2): forward the dispatcher's pre-collected
191+
// password so this glob-resolution step does NOT re-prompt
192+
// after the dispatcher already asked once.
193+
params.ssh_password.clone(),
188194
)
189195
.await?;
190196

src/commands/exec.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ use std::sync::Arc;
1919
use crate::executor::{ExitCodeStrategy, OutputMode, ParallelExecutor, RankDetector};
2020
use crate::forwarding::ForwardingType;
2121
use crate::node::Node;
22-
use crate::security::SudoPassword;
22+
use crate::security::{Password, SudoPassword};
2323
use crate::ssh::SshConfig;
2424
use crate::ssh::known_hosts::StrictHostKeyChecking;
2525
use crate::ssh::tokio_client::SshConnectionConfig;
@@ -35,6 +35,10 @@ pub struct ExecuteCommandParams<'a> {
3535
pub strict_mode: StrictHostKeyChecking,
3636
pub use_agent: bool,
3737
pub use_password: bool,
38+
/// Pre-collected SSH password collected once by the dispatcher and shared
39+
/// (via `Arc::clone`) with every per-node SSH connection task. When
40+
/// `use_password` is `true`, this should be `Some(_)`.
41+
pub ssh_password: Option<Arc<Password>>,
3842
#[cfg(target_os = "macos")]
3943
pub use_keychain: bool,
4044
pub output_dir: Option<&'a Path>,
@@ -109,10 +113,21 @@ async fn execute_command_with_forwarding(params: ExecuteCommandParams<'_>) -> Re
109113
return Err(anyhow::anyhow!("SSH agent not supported on Windows"));
110114
}
111115
} else if params.use_password {
112-
// For password auth, we'd need to prompt - for now return error
113-
return Err(anyhow::anyhow!(
114-
"Password authentication not yet supported with port forwarding"
115-
));
116+
// Issue #200 (M1): consume the dispatcher's pre-collected password
117+
// instead of erroring out. Previously this branch returned
118+
// "Password authentication not yet supported with port forwarding"
119+
// even though the dispatcher had already prompted the user — a
120+
// confusing UX regression. The dispatcher collects unconditionally
121+
// for `exec`, so when `use_password` is true we always have one.
122+
let Some(password) = params.ssh_password.as_ref() else {
123+
anyhow::bail!(
124+
"--password was requested for port-forwarding exec but no \
125+
password was collected up-front (programmer error in dispatcher: \
126+
the `exec` arm must populate `ExecuteCommandParams::ssh_password` \
127+
when `use_password` is true)."
128+
);
129+
};
130+
AuthMethod::with_password(password.as_str())
116131
} else {
117132
// Use default key file authentication
118133
let key_path = params
@@ -218,6 +233,7 @@ async fn execute_command_without_forwarding(params: ExecuteCommandParams<'_>) ->
218233
.with_connect_timeout(params.connect_timeout)
219234
.with_jump_hosts(params.jump_hosts.map(|s| s.to_string()))
220235
.with_sudo_password(params.sudo_password)
236+
.with_ssh_password(params.ssh_password)
221237
.with_batch_mode(params.batch)
222238
.with_fail_fast(params.fail_fast)
223239
.with_ssh_config(params.ssh_config.cloned())

src/commands/interactive/connection.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,8 @@ impl InteractiveCommand {
161161
auth_ctx = auth_ctx
162162
.with_agent(self.use_agent)
163163
.with_password(self.use_password)
164-
.with_password_fallback(!self.use_password); // Enable fallback only if not using explicit password
164+
.with_password_fallback(!self.use_password) // Enable fallback only if not using explicit password
165+
.with_pre_collected_password(self.ssh_password.clone());
165166

166167
// Set macOS Keychain integration if available
167168
#[cfg(target_os = "macos")]
@@ -262,11 +263,14 @@ impl InteractiveCommand {
262263
.min(MAX_TIMEOUT_SECS),
263264
);
264265

265-
// Pass SSH connection config to jump host chain for keepalive settings
266+
// Pass SSH connection config to jump host chain for keepalive settings.
267+
// Also pass the dispatcher's pre-collected password so jump-host
268+
// authentication consumes it instead of re-prompting per call. See #200.
266269
let chain = JumpHostChain::new(jump_hosts)
267270
.with_connect_timeout(adjusted_timeout)
268271
.with_command_timeout(Duration::from_secs(300))
269-
.with_ssh_connection_config(self.ssh_connection_config.clone());
272+
.with_ssh_connection_config(self.ssh_connection_config.clone())
273+
.with_ssh_password(self.ssh_password.clone());
270274

271275
// Connect through the chain
272276
let connection = timeout(
@@ -406,11 +410,14 @@ impl InteractiveCommand {
406410
.min(MAX_TIMEOUT_SECS),
407411
);
408412

409-
// Pass SSH connection config to jump host chain for keepalive settings
413+
// Pass SSH connection config to jump host chain for keepalive settings.
414+
// Also pass the dispatcher's pre-collected password so jump-host
415+
// authentication consumes it instead of re-prompting per call. See #200.
410416
let chain = JumpHostChain::new(jump_hosts)
411417
.with_connect_timeout(adjusted_timeout)
412418
.with_command_timeout(Duration::from_secs(300))
413-
.with_ssh_connection_config(self.ssh_connection_config.clone());
419+
.with_ssh_connection_config(self.ssh_connection_config.clone())
420+
.with_ssh_password(self.ssh_password.clone());
414421

415422
// Connect through the chain
416423
let connection = timeout(

0 commit comments

Comments
 (0)