-
Notifications
You must be signed in to change notification settings - Fork 36
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support user password login ssh #129
Changes from all commits
d29a37e
5d4042d
40840c5
7dd664b
8ba00de
385f70d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,7 @@ use std::{fs, io}; | |
use dirs::state_dir; | ||
use once_cell::sync::OnceCell; | ||
use tempfile::{Builder, TempDir}; | ||
use tokio::process; | ||
use tokio::process::Command; | ||
|
||
/// The returned `&'static Path` can be coreced to any lifetime. | ||
fn get_default_control_dir<'a>() -> Result<&'a Path, Error> { | ||
|
@@ -60,6 +60,7 @@ pub struct SessionBuilder { | |
user: Option<String>, | ||
port: Option<String>, | ||
keyfile: Option<PathBuf>, | ||
password: Option<String>, | ||
connect_timeout: Option<String>, | ||
server_alive_interval: Option<u64>, | ||
known_hosts_check: KnownHosts, | ||
|
@@ -78,6 +79,7 @@ impl Default for SessionBuilder { | |
user: None, | ||
port: None, | ||
keyfile: None, | ||
password: None, | ||
connect_timeout: None, | ||
server_alive_interval: None, | ||
known_hosts_check: KnownHosts::Add, | ||
|
@@ -106,8 +108,8 @@ impl SessionBuilder { | |
/// Set the ssh user (`ssh -l`). | ||
/// | ||
/// Defaults to `None`. | ||
pub fn user(&mut self, user: String) -> &mut Self { | ||
self.user = Some(user); | ||
pub fn user(&mut self, user: impl AsRef<str>) -> &mut Self { | ||
self.user = Some(user.as_ref().to_string()); | ||
self | ||
} | ||
|
||
|
@@ -127,6 +129,14 @@ impl SessionBuilder { | |
self | ||
} | ||
|
||
/// Set the ssh password. | ||
/// | ||
/// Defaults to `None`. | ||
pub fn password(&mut self, p: impl AsRef<str>) -> &mut Self { | ||
self.password = Some(p.as_ref().to_string()); | ||
self | ||
} | ||
|
||
/// See [`KnownHosts`]. | ||
/// | ||
/// Default `KnownHosts::Add`. | ||
|
@@ -337,7 +347,7 @@ impl SessionBuilder { | |
|
||
let mut with_overrides = self.clone(); | ||
if let Some(user) = user { | ||
with_overrides.user(user.to_owned()); | ||
with_overrides.user(user); | ||
} | ||
|
||
if let Some(port) = port { | ||
|
@@ -347,6 +357,54 @@ impl SessionBuilder { | |
(Cow::Owned(with_overrides), destination) | ||
} | ||
|
||
/// Create ssh master Command | ||
fn init_command(&self, log: &PathBuf, master: &PathBuf) -> Command { | ||
if let Some(pass) = &self.password { | ||
let mut init = Command::new("sshpass"); | ||
|
||
init.stdin(Stdio::null()) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we deduplicate the code here? Most of the args provided are the same |
||
.stdout(Stdio::null()) | ||
.stderr(Stdio::null()) | ||
.arg("-p") | ||
.arg(pass) | ||
.arg("ssh") | ||
.arg("-E") | ||
.arg(log) | ||
.arg("-S") | ||
.arg(master) | ||
.arg("-M") | ||
.arg("-f") | ||
.arg("-N") | ||
.arg("-o") | ||
.arg("ControlPersist=yes") | ||
.arg("-o") | ||
.arg("BatchMode=no") | ||
.arg("-o") | ||
.arg(self.known_hosts_check.as_option()); | ||
return init; | ||
} | ||
|
||
let mut init = Command::new("ssh"); | ||
|
||
init.stdin(Stdio::null()) | ||
.stdout(Stdio::null()) | ||
.stderr(Stdio::null()) | ||
.arg("-E") | ||
.arg(log) | ||
.arg("-S") | ||
.arg(master) | ||
.arg("-M") | ||
.arg("-f") | ||
.arg("-N") | ||
.arg("-o") | ||
.arg("ControlPersist=yes") | ||
.arg("-o") | ||
.arg("BatchMode=yes") | ||
.arg("-o") | ||
.arg(self.known_hosts_check.as_option()); | ||
init | ||
} | ||
|
||
/// Create ssh master session and return [`TempDir`] which | ||
/// contains the ssh control socket. | ||
pub async fn launch_master(&self, destination: &str) -> Result<TempDir, Error> { | ||
|
@@ -368,25 +426,9 @@ impl SessionBuilder { | |
.map_err(Error::Master)?; | ||
|
||
let log = dir.path().join("log"); | ||
let master = dir.path().join("master"); | ||
|
||
let mut init = process::Command::new("ssh"); | ||
|
||
init.stdin(Stdio::null()) | ||
.stdout(Stdio::null()) | ||
.stderr(Stdio::null()) | ||
.arg("-E") | ||
.arg(&log) | ||
.arg("-S") | ||
.arg(dir.path().join("master")) | ||
.arg("-M") | ||
.arg("-f") | ||
.arg("-N") | ||
.arg("-o") | ||
.arg("ControlPersist=yes") | ||
.arg("-o") | ||
.arg("BatchMode=yes") | ||
.arg("-o") | ||
.arg(self.known_hosts_check.as_option()); | ||
let mut init = self.init_command(&log, &master); | ||
|
||
if let Some(ref timeout) = self.connect_timeout { | ||
init.arg("-o").arg(format!("ConnectTimeout={}", timeout)); | ||
|
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
@@ -180,9 +180,10 @@ impl Session { | |||||||||||||
/// The format of `destination` is the same as the `destination` argument to `ssh`. It may be | ||||||||||||||
/// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. | ||||||||||||||
/// | ||||||||||||||
/// The function provide keypair-based authentication. | ||||||||||||||
/// | ||||||||||||||
/// If connecting requires interactive authentication based on `STDIN` (such as reading a | ||||||||||||||
/// password), the connection will fail. Consider setting up keypair-based authentication | ||||||||||||||
/// instead. | ||||||||||||||
/// password), consider use [`Session::connect_with_pass`] function instead. | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||
/// | ||||||||||||||
/// For more options, see [`SessionBuilder`]. | ||||||||||||||
#[cfg(feature = "process-mux")] | ||||||||||||||
|
@@ -194,6 +195,35 @@ impl Session { | |||||||||||||
.await | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/// Connect to the host at the given `host` over SSH using process impl, which will | ||||||||||||||
/// spawn a new ssh process for each `Child` created. | ||||||||||||||
/// | ||||||||||||||
/// The format of `destination` is the same as the `destination` argument to `ssh`. It may be | ||||||||||||||
/// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. | ||||||||||||||
/// | ||||||||||||||
/// The function provide interactive authentication based on `STDIN` (such as reading a | ||||||||||||||
/// password). | ||||||||||||||
/// | ||||||||||||||
/// Warning ⚠️: current use sshpass non-interactive ssh password auth. | ||||||||||||||
/// See detail: <https://sourceforge.net/projects/sshpass/> | ||||||||||||||
/// | ||||||||||||||
/// The current implementation of sshpass is only temporary, and the functionality will be | ||||||||||||||
/// optimized later. If using this feature, pay attention to security risks. | ||||||||||||||
/// | ||||||||||||||
/// For more options, see [`SessionBuilder`]. | ||||||||||||||
#[cfg(feature = "process-mux")] | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to put it under another feature "ssh-pass-login" since in the future we will implement this ourselves using pty, which will pull in additional features most don't use. |
||||||||||||||
#[cfg_attr(docsrs, doc(cfg(feature = "process-mux")))] | ||||||||||||||
pub async fn connect_with_pass<S: AsRef<str>>( | ||||||||||||||
destination: S, | ||||||||||||||
pass: impl AsRef<str>, | ||||||||||||||
) -> Result<Self, Error> { | ||||||||||||||
SessionBuilder::default() | ||||||||||||||
.password(pass) | ||||||||||||||
.known_hosts_check(KnownHosts::Accept) | ||||||||||||||
.connect(destination) | ||||||||||||||
.await | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/// Connect to the host at the given `host` over SSH using native mux impl, which | ||||||||||||||
/// will create a new socket connection for each `Child` created. | ||||||||||||||
/// | ||||||||||||||
|
@@ -202,9 +232,10 @@ impl Session { | |||||||||||||
/// The format of `destination` is the same as the `destination` argument to `ssh`. It may be | ||||||||||||||
/// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. | ||||||||||||||
/// | ||||||||||||||
/// The function provide keypair-based authentication. | ||||||||||||||
/// | ||||||||||||||
/// If connecting requires interactive authentication based on `STDIN` (such as reading a | ||||||||||||||
/// password), the connection will fail. Consider setting up keypair-based authentication | ||||||||||||||
/// instead. | ||||||||||||||
/// password), consider use [`Session::connect_mux_with_pass`] function instead. | ||||||||||||||
/// | ||||||||||||||
/// For more options, see [`SessionBuilder`]. | ||||||||||||||
#[cfg(feature = "native-mux")] | ||||||||||||||
|
@@ -219,6 +250,37 @@ impl Session { | |||||||||||||
.await | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/// Connect to the host at the given `host` over SSH using native mux impl, which | ||||||||||||||
/// will create a new socket connection for each `Child` created. | ||||||||||||||
/// | ||||||||||||||
/// See the crate-level documentation for more details on the difference between native and process-based mux. | ||||||||||||||
/// | ||||||||||||||
/// The format of `destination` is the same as the `destination` argument to `ssh`. It may be | ||||||||||||||
/// specified as either `[user@]hostname` or a URI of the form `ssh://[user@]hostname[:port]`. | ||||||||||||||
/// | ||||||||||||||
/// The function provide interactive authentication based on `STDIN` (such as reading a | ||||||||||||||
/// password). | ||||||||||||||
/// | ||||||||||||||
/// Warning ⚠️: current use sshpass non-interactive ssh password auth. | ||||||||||||||
/// See detail: <https://sourceforge.net/projects/sshpass/> | ||||||||||||||
/// | ||||||||||||||
/// The current implementation of sshpass is only temporary, and the functionality will be | ||||||||||||||
/// optimized later. If using this feature, pay attention to security risks. | ||||||||||||||
/// | ||||||||||||||
/// For more options, see [`SessionBuilder`]. | ||||||||||||||
#[cfg(feature = "native-mux")] | ||||||||||||||
#[cfg_attr(docsrs, doc(cfg(feature = "native-mux")))] | ||||||||||||||
pub async fn connect_mux_with_pass<S: AsRef<str>>( | ||||||||||||||
destination: S, | ||||||||||||||
pass: impl AsRef<str>, | ||||||||||||||
) -> Result<Self, Error> { | ||||||||||||||
SessionBuilder::default() | ||||||||||||||
.password(pass) | ||||||||||||||
.known_hosts_check(KnownHosts::Accept) | ||||||||||||||
.connect_mux(destination) | ||||||||||||||
.await | ||||||||||||||
} | ||||||||||||||
|
||||||||||||||
/// Check the status of the underlying SSH connection. | ||||||||||||||
#[cfg(not(windows))] | ||||||||||||||
#[cfg_attr(docsrs, doc(cfg(not(windows))))] | ||||||||||||||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMHO we can use zeroize here to avoid leaking the password.