Pure-Rust asynchronous SSH library, both client and server

#20 An layman example

Closed on February 1, 2021
imbolc on February 1, 2021

Hi :) Thanks for the great lib! I haven’t found a simple example on how to run a shell command remotely. I’m not quite familiar with ssh protocol either, so I’ve just adapted your example from the docs. I’m not sure how correct it is, but at least it works :) Maybe you’d consider to add it to examples:

use anyhow::Result;
use std::io::Write;
use std::sync::Arc;
use thrussh::*;
use thrussh_keys::*;

#[tokio::main]
async fn main() -> Result<()> {
    let pem = std::fs::read("./my-aws-key.pem")?;
    let mut ssh = Session::connect(&pem, "ubuntu", "35.158.158.35:22").await?;
    let r = ssh.call("whoami").await?;
    assert!(r.success());
    assert_eq!(r.output(), "ubuntu\n");
    ssh.close().await?;
    Ok(())
}

struct Client {}

impl client::Handler for Client {
    type Error = thrussh::Error;
    type FutureUnit = futures::future::Ready<Result<(Self, client::Session), Self::Error>>;
    type FutureBool = futures::future::Ready<Result<(Self, bool), Self::Error>>;

    fn finished_bool(self, b: bool) -> Self::FutureBool {
        futures::future::ready(Ok((self, b)))
    }
    fn finished(self, session: client::Session) -> Self::FutureUnit {
        futures::future::ready(Ok((self, session)))
    }
    fn check_server_key(self, _server_public_key: &key::PublicKey) -> Self::FutureBool {
        self.finished_bool(true)
    }
}

pub struct Session {
    session: client::Handle<Client>,
}

impl Session {
    async fn connect(
        pem: &[u8],
        user: impl Into<String>,
        addr: impl std::net::ToSocketAddrs,
    ) -> Result<Self> {
        let key_pair = key::KeyPair::RSA {
            key: openssl::rsa::Rsa::private_key_from_pem(pem)?,
            hash: key::SignatureHash::SHA2_512,
        };
        let config = client::Config::default();
        let config = Arc::new(config);
        let sh = Client {};
        let mut agent = agent::client::AgentClient::connect_env().await?;
        agent.add_identity(&key_pair, &[]).await?;
        let mut identities = agent.request_identities().await?;
        let mut session = client::connect(config, addr, sh).await?;
        let pubkey = identities.pop().unwrap();
        let (_, auth_res) = session.authenticate_future(user, pubkey, agent).await;
        let _auth_res = auth_res?;
        Ok(Self { session })
    }

    async fn call(&mut self, command: &str) -> Result<CommandResult> {
        let mut channel = self.session.channel_open_session().await?;
        channel.exec(true, command).await?;
        let mut output = Vec::new();
        let mut code = None;
        while let Some(msg) = channel.wait().await {
            match msg {
                thrussh::ChannelMsg::Data { ref data } => {
                    output.write_all(&data).unwrap();
                }
                thrussh::ChannelMsg::ExitStatus { exit_status } => {
                    code = Some(exit_status);
                }
                _ => {}
            }
        }
        Ok(CommandResult { output, code })
    }

    async fn close(&mut self) -> Result<()> {
        self.session
            .disconnect(Disconnect::ByApplication, "", "English")
            .await?;
        Ok(())
    }
}

struct CommandResult {
    output: Vec<u8>,
    code: Option<u32>,
}

impl CommandResult {
    fn output(&self) -> String {
        String::from_utf8_lossy(&self.output).into()
    }

    fn success(&self) -> bool {
        self.code == Some(0)
    }
}
pmeunier on February 1, 2021

Thanks! Great example. Do you want to send me a Pijul patch?

imbolc added a change on February 1, 2021
KGIUIQYIBBUEGBOQIFYJJNZGGSPV3KERBPYTKKCOBKZZ5CHIJ24AC
main
imbolc on February 1, 2021

Are you planing to add a higher level api, btw? I’d be glad to contribute, but it would require mentorship as I’m quite new to rust.

pmeunier on February 1, 2021

The goal is to keep the base library small and auditable, but I’d love to help you build something higher level on top of it, in a different crate, possibly with a binary like OpenSSH’s “ssh”. Since there is already support for agents, “ssh-agent” would be doable as well.

I can totally mentor you if you want.

pmeunier closed this discussion on February 1, 2021