Closed fadedbee closed 1 year ago
The example server will accept any ed25519
type client key, or also ssh-rsa
if you build it with --all-features
Sorry, I tried cargo build --release --all-features
followed by target/release/sshserver
and the behaviour didn't change. Am I using --all-features
correctly?
Yes but you still need to make sure you're using a supported key type to authenticate, or select it explicitly with ssh -i.
Thanks for your help with this. Does this mean that Russh is not suitable for an internet-facing SSH interface where you cannot control what types of keys the SSH clients have? (I was naively expecting to run the example server and to be able to connect from any SSH client less than 5 years old.)
Of course it is, ssh-rsa
has been the default key type in OpenSSH for a decade now. Just generate one with ssh-keygen
and you should be good to go.
Deleting my .ssh directory and making a new RSA key does not solve the problem. Am I missing something obvious here?
fadedbee@box:~$ ls -lA .ssh
ls: cannot access '.ssh': No such file or directory
fadedbee@box:~$ ssh-keygen
Generating public/private rsa key pair.
Enter file in which to save the key (/home/fadedbee/.ssh/id_rsa):
Created directory '/home/fadedbee/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/fadedbee/.ssh/id_rsa.
Your public key has been saved in /home/fadedbee/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:Fr2FptpCg3GjUfjTs+/uaZsbOCf3Tn5OpGth7AcTaYE fadedbee@box
The key's randomart image is:
+---[RSA 2048]----+
| .. . |
| .. .E.. |
| o.o.. + .o |
| *o.o+ o+ |
| o o.So.o .. |
| . =.. *o |
| o =.+oo+. |
| . =oBooo |
| =O==+. |
+----[SHA256]-----+
fadedbee@box:~$ ls -lA .ssh
total 8
-rw------- 1 fadedbee fadedbee 1679 Feb 18 12:10 id_rsa
-rw-r--r-- 1 fadedbee fadedbee 395 Feb 18 12:10 id_rsa.pub
fadedbee@box:~$ cat .ssh/id_rsa.pub
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC33bxmYvGmIu0HDsdfWZTf3fE2SnveOd5h1VjBRkyBDVRbsiBiUBo7cGsSKkFCEBWBOa/jCivMJrm6EpsObi0iUJ8MQ1DRjGVIlYXfgSv/yNumdysur9Yuv0KYopVkEuxYyAr+ElCsuWUiQqxqEA+qIKEi3OFxSN8T1UiENR/R2NqaiN4IELYcbOcgjCJgsLGN0HpphijxR6swPlWR9PHRDd54mQIFawrtazS1OImEHpchSUghSaJpQ0CzRqWrLb2W5GbHQwhLDQr4GbYwZMT4ZTCWVLf9hCZVUEVxnzUBDXaDQrCZpdDfvGIwrELPQOT2/YTbMV6dfYNNuodzcZoP fadedbee@box
fadedbee@box:~$ ssh localhost -p 2222 -v
OpenSSH_7.6p1 Ubuntu-4ubuntu0.7, OpenSSL 1.0.2n 7 Dec 2017
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 19: Applying options for *
debug1: Connecting to localhost [127.0.0.1] port 2222.
debug1: Connection established.
debug1: identity file /home/fadedbee/.ssh/id_rsa type 0
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_rsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_dsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_dsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_ecdsa type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_ecdsa-cert type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_ed25519 type -1
debug1: key_load_public: No such file or directory
debug1: identity file /home/fadedbee/.ssh/id_ed25519-cert type -1
debug1: Local version string SSH-2.0-OpenSSH_7.6p1 Ubuntu-4ubuntu0.7
debug1: Remote protocol version 2.0, remote software version russh_0.36.1
debug1: no match: russh_0.36.1
debug1: Authenticating to localhost:2222 as 'fadedbee'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256@libssh.org
debug1: kex: host key algorithm: ssh-ed25519
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
debug1: Server host key: ssh-ed25519 SHA256:LOsOQOLAdyJuJ51G22F/m7wv/Fn405EHzdr9EjXDIN0
debug1: checking without port identifier
The authenticity of host '[localhost]:2222 ([127.0.0.1]:2222)' can't be established.
ED25519 key fingerprint is SHA256:LOsOQOLAdyJuJ51G22F/m7wv/Fn405EHzdr9EjXDIN0.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[localhost]:2222' (ED25519) to the list of known hosts.
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=<ssh-rsa,ssh-ed25519,rsa-sha2-256,rsa-sha2-512>
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: password,publickey,hostbased,keyboard-interactive
debug1: Next authentication method: publickey
debug1: Offering public key: RSA SHA256:Fr2FptpCg3GjUfjTs+/uaZsbOCf3Tn5OpGth7AcTaYE /home/fadedbee/.ssh/id_rsa
debug1: Server accepts key: pkalg rsa-sha2-512 blen 279
debug1: Authentication succeeded (publickey).
Authenticated to localhost ([127.0.0.1]:2222).
debug1: channel 0: new [client-session]
debug1: Entering interactive session.
debug1: pledge: network
debug1: Sending environment.
debug1: Sending env LANG = en_GB.UTF-8
debug1: channel 0: free: client-session, nchannels 1
Connection to localhost closed by remote host.
Connection to localhost closed.
Transferred: sent 2988, received 1464 bytes, in 3.0 seconds
Bytes per second: sent 995.0, received 487.5
debug1: Exit status -1
fadedbee@box:~$
Looks like it did? Once you see "entering interactive session", you can press any keys and they should be broadcast to other connected clients.
No, there is no time, see: https://asciinema.org/a/xTQnJkoNcUeq54l3ZISyeIjaB
The full code of what I'm running, just in case I've used a wrong dependency or broken something is:
Cargo.toml:
[package]
name = "sshserver"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.69"
async-trait = "0.1.64"
env_logger = "0.10.0"
futures = "0.3.26"
log = "0.4.17"
russh = { version = "0.36.1", features = ["openssl", "vendored-openssl"] }
russh-keys = { version = "0.24.1", features = ["openssl", "vendored-openssl"] }
tokio = "1.25.0"
(I'd previously just used default features, but I then added all features, just in case.)
main.rs:
use async_trait::async_trait;
use futures::Future;
use log::debug;
use russh::server::{Auth, Msg, Session};
use russh::*;
use russh_keys::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client_key = russh_keys::key::KeyPair::generate_ed25519().unwrap();
let client_pubkey = Arc::new(client_key.clone_public_key().unwrap());
let mut config = russh::server::Config::default();
config.connection_timeout = Some(std::time::Duration::from_secs(3));
config.auth_rejection_time = std::time::Duration::from_secs(3);
config
.keys
.push(russh_keys::key::KeyPair::generate_ed25519().unwrap());
let config = Arc::new(config);
let sh = Server {
client_pubkey,
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};
tokio::time::timeout(
std::time::Duration::from_secs(60),
russh::server::run(config, ("0.0.0.0", 2222), sh),
)
.await
.unwrap_or(Ok(()))?;
Ok(())
}
#[derive(Clone)]
struct Server {
client_pubkey: Arc<russh_keys::key::PublicKey>,
clients: Arc<Mutex<HashMap<(usize, ChannelId), Channel<Msg>>>>,
id: usize,
}
impl server::Server for Server {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
debug!("new client");
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl server::Handler for Server {
type Error = anyhow::Error;
async fn channel_open_session(
self,
channel: Channel<Msg>,
session: Session,
) -> Result<(Self, bool, Session), Self::Error> {
{
debug!("channel open session");
let mut clients = self.clients.lock().unwrap();
clients.insert((self.id, channel.id()), channel);
}
Ok((self, true, session))
}
async fn auth_publickey(
self,
_: &str,
_: &key::PublicKey,
) -> Result<(Self, Auth), Self::Error> {
Ok((self, server::Auth::Accept))
}
async fn data(
self,
channel: ChannelId,
data: &[u8],
mut session: Session,
) -> Result<(Self, Session), Self::Error> {
{
debug!("data: {data:?}");
let mut clients = self.clients.lock().unwrap();
for ((id, _channel_id), ref mut channel) in clients.iter_mut() {
channel.data(data);
}
}
Ok((self, session))
}
}
(I've probably done a cargo fmt
, as well as adding a couple of lines of logging/debugging and extending the timeout.)
The results of running the above are:
fadedbee@box ~/sshserver $ RUST_LOG=debug cargo run
warning: unused import: `futures::Future`
--> src/main.rs:2:5
|
2 | use futures::Future;
| ^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_imports)]` on by default
warning: unused variable: `channel`
--> src/main.rs:78:9
|
78 | channel: ChannelId,
| ^^^^^^^ help: if this is intentional, prefix it with an underscore: `_channel`
|
= note: `#[warn(unused_variables)]` on by default
warning: unused variable: `id`
--> src/main.rs:85:19
|
85 | for ((id, _channel_id), ref mut channel) in clients.iter_mut() {
| ^^ help: if this is intentional, prefix it with an underscore: `_id`
warning: variable does not need to be mutable
--> src/main.rs:80:9
|
80 | mut session: Session,
| ----^^^^^^^
| |
| help: remove this `mut`
|
= note: `#[warn(unused_mut)]` on by default
warning: field `client_pubkey` is never read
--> src/main.rs:38:5
|
37 | struct Server {
| ------ field in this struct
38 | client_pubkey: Arc<russh_keys::key::PublicKey>,
| ^^^^^^^^^^^^^
|
= note: `#[warn(dead_code)]` on by default
= note: `Server` has a derived impl for the trait `Clone`, but this is intentionally ignored during dead code analysis
warning: unused implementer of `futures::Future` that must be used
--> src/main.rs:86:17
|
86 | channel.data(data);
| ^^^^^^^^^^^^^^^^^^^
|
= note: `#[warn(unused_must_use)]` on by default
= note: futures do nothing unless you `.await` or poll them
warning: `sshserver` (bin "sshserver") generated 6 warnings
Finished dev [unoptimized + debuginfo] target(s) in 0.09s
Running `target/debug/sshserver`
fadedbee@box ~/sshserver $
I just noticed two 3 second timeouts which I'd overlooked:
config.connection_timeout = Some(std::time::Duration::from_secs(3));
config.auth_rejection_time = std::time::Duration::from_secs(3);
Changing these to 60 just makes disconnection take longer. No data is relayed between connections, or logged on the server.
Once the ssh client session exits, all of the keypresses get executed as commands, which makes me think that they have not been accepted by ssh and sent to the server. (Luckily I was typing asdfasfd
not sudo rm -rf /
.)
Figured it out! The example actually had the connection timeout set to 3 seconds - here is the fixed version:
use async_trait::async_trait;
use env_logger;
use futures::Future;
use log::debug;
use russh::server::{Auth, Msg, Session};
use russh::*;
use russh_keys::*;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
env_logger::init();
let client_key = russh_keys::key::KeyPair::generate_ed25519().unwrap();
let client_pubkey = Arc::new(client_key.clone_public_key().unwrap());
let mut config = russh::server::Config::default();
config.auth_rejection_time = std::time::Duration::from_secs(3);
config
.keys
.push(russh_keys::key::KeyPair::generate_ed25519().unwrap());
let config = Arc::new(config);
let sh = Server {
client_pubkey,
clients: Arc::new(Mutex::new(HashMap::new())),
id: 0,
};
tokio::time::timeout(
std::time::Duration::from_secs(60),
russh::server::run(config, ("0.0.0.0", 2222), sh),
)
.await
.unwrap_or(Ok(()))?;
Ok(())
}
#[derive(Clone)]
struct Server {
client_pubkey: Arc<russh_keys::key::PublicKey>,
clients: Arc<Mutex<HashMap<(usize, ChannelId), Channel<Msg>>>>,
id: usize,
}
impl server::Server for Server {
type Handler = Self;
fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
debug!("new client");
let s = self.clone();
self.id += 1;
s
}
}
#[async_trait]
impl server::Handler for Server {
type Error = anyhow::Error;
async fn channel_open_session(
self,
channel: Channel<Msg>,
session: Session,
) -> Result<(Self, bool, Session), Self::Error> {
{
debug!("channel open session");
let mut clients = self.clients.lock().unwrap();
clients.insert((self.id, channel.id()), channel);
}
Ok((self, true, session))
}
/// The client requests a shell.
#[allow(unused_variables)]
async fn shell_request(
self,
channel: ChannelId,
mut session: Session,
) -> Result<(Self, Session), Self::Error> {
session.request_success();
Ok((self, session))
}
async fn auth_publickey(
self,
_: &str,
_: &key::PublicKey,
) -> Result<(Self, Auth), Self::Error> {
Ok((self, server::Auth::Accept))
}
async fn data(
self,
_channel: ChannelId,
data: &[u8],
mut session: Session,
) -> Result<(Self, Session), Self::Error> {
debug!("data: {data:?}");
{
let mut clients = self.clients.lock().unwrap();
for ((_, _channel_id), ref mut channel) in clients.iter_mut() {
session.data(channel.id(), CryptoVec::from(data.to_vec()));
}
}
Ok((self, session))
}
}
Thanks, I'm now using your code above. The significant change was the addition of shell_request
. (I'd spotted and changed the 3 second timeouts earlier.) It almost works now. SSH clients get their input echoed back to them, but not sent to each other. i.e. if I press a
I see aa
.
Before I jump into it any further, could you please try the included echoserver
example? It's known working unlike this old example from the thrussh
times.
I can confirm that the echoserver
works correctly. Could you remove the "example server" from https://docs.rs/russh/0.36.1/russh/server/index.html ? Having a broken example as the first contact with this project will cause many prospective users to walk away.
I first attempted to use Thrush a few years ago, but couldn't get the example server to work back then, so thank your for all your help.
I'm using the example server from: https://docs.rs/russh/latest/russh/server/index.html but with a 60 second timeout. (Otherwise it exits after just one second.)
When I try:
ssh localhost -p 2222 -v
I see:What do I need to add to the example server to allow SSH clients to connect?