Skip to content
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

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 64 additions & 22 deletions src/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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> {
Expand Down Expand Up @@ -60,6 +60,7 @@ pub struct SessionBuilder {
user: Option<String>,
port: Option<String>,
keyfile: Option<PathBuf>,
password: Option<String>,
Copy link
Member

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.

connect_timeout: Option<String>,
server_alive_interval: Option<u64>,
known_hosts_check: KnownHosts,
Expand All @@ -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,
Expand Down Expand Up @@ -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
}

Expand All @@ -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`.
Expand Down Expand Up @@ -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 {
Expand All @@ -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())
Copy link
Member

Choose a reason for hiding this comment

The 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> {
Expand All @@ -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));
Expand Down
70 changes: 66 additions & 4 deletions src/session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// password), consider use [`Session::connect_with_pass`] function instead.
/// password), consider use [`Session::connect_with_pass`] function instead, though do note
/// that it passes the password via cmdline currently,
/// so it might leak your password to others
/// using the same machine and have privileges
/// to check /proc/$ssh_pid/cmdline

///
/// For more options, see [`SessionBuilder`].
#[cfg(feature = "process-mux")]
Expand All @@ -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")]
Copy link
Member

Choose a reason for hiding this comment

The 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.
///
Expand All @@ -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")]
Expand All @@ -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))))]
Expand Down
Loading