Skip to content

Commit

Permalink
Add TestServer to test SSH connections.
Browse files Browse the repository at this point in the history
Also add a very basic test to check the ssh_connect NASL function.
  • Loading branch information
Tehforsch committed Oct 1, 2024
1 parent 16be56e commit 2b63150
Show file tree
Hide file tree
Showing 8 changed files with 760 additions and 51 deletions.
578 changes: 574 additions & 4 deletions rust/Cargo.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -96,12 +96,14 @@ members = [
[dev-dependencies]
tracing-test = "0.2.5"
criterion = "0"
russh = "0.45.0"
russh-keys = "0.45.0"

[features]
dep-graph-parallel = ["rayon", "crossbeam-channel"]
openvas_serde_support = []
serde_support = []
default = ["dep-graph-parallel", "openvas_serde_support", "enforce-no-trailing-arguments", "serde_support"]
default = ["dep-graph-parallel", "openvas_serde_support", "enforce-no-trailing-arguments", "serde_support", "experimental"]

nasl-builtin-raw-ip = ["pcap", "pnet_base", "pnet", "socket2", "pnet_macros", "pnet_macros_support",]
nasl-builtin-ssh = ["libssh-rs"]
Expand Down
44 changes: 13 additions & 31 deletions rust/src/nasl/builtin/ssh/libssh/session.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use libssh_rs::{AuthMethods, AuthStatus, InteractiveAuthInfo, Session, Sftp, SshKey, SshOption};
use std::{os::fd::AsRawFd, time::Duration};
use tokio::sync::MutexGuard;
use tracing::{debug, info};

use crate::nasl::builtin::ssh::SessionId;
Expand Down Expand Up @@ -36,45 +35,29 @@ impl SshSession {
}
}

pub struct BorrowedSession<'a> {
guard: MutexGuard<'a, SshSession>,
}

impl<'a> BorrowedSession<'a> {
pub fn new(guard: MutexGuard<'a, SshSession>) -> Self {
Self { guard }
}

fn borrow(&self) -> &SshSession {
&self.guard
}

fn borrow_mut(&mut self) -> &mut SshSession {
&mut self.guard
}

impl SshSession {
fn session(&self) -> &Session {
&self.borrow().session
&self.session
}

pub fn id(&self) -> SessionId {
self.borrow().id
self.id
}

fn channel(&self) -> &Option<Channel> {
&self.borrow().channel
&self.channel
}

pub fn set_channel(&mut self, channel: Channel) {
self.borrow_mut().channel = Some(channel);
self.channel = Some(channel);
}

fn authmethods(&self) -> Option<AuthMethods> {
self.borrow().authmethods
self.authmethods
}

fn user_set(&self) -> bool {
self.borrow().user_set
self.user_set
}

pub fn new_channel(&self) -> Result<Channel> {
Expand Down Expand Up @@ -115,7 +98,7 @@ impl<'a> BorrowedSession<'a> {
debug!("Encountered error while closing channel: {}", e);
}
}
self.borrow_mut().channel = None;
self.channel = None;
}

pub fn ensure_user_set(&mut self, login: Option<&str>) -> Result<()> {
Expand All @@ -136,12 +119,12 @@ impl<'a> BorrowedSession<'a> {
let mut channel = self.new_channel()?;
channel.open_session()?;
self.request_ssh_shell(&mut channel, pty)?;
self.borrow_mut().channel = Some(channel);
self.channel = Some(channel);
Ok(())
}

pub fn disconnect(&mut self) -> Result<()> {
if let Some(ref channel) = self.borrow_mut().channel {
if let Some(ref channel) = self.channel {
channel.close()?;
}
self.session().disconnect();
Expand Down Expand Up @@ -206,7 +189,7 @@ impl<'a> BorrowedSession<'a> {
}
}
};
self.borrow_mut().authmethods = Some(authmethods);
self.authmethods = Some(authmethods);
Ok(authmethods)
}
}
Expand All @@ -218,14 +201,13 @@ impl<'a> BorrowedSession<'a> {
macro_rules! inherit_method {
($name: ident, $ret: ty, $err_variant: ident $(,)? $($arg: ident : $argtype: ty),*) => {
pub fn $name(&self, $($arg: $argtype),*) -> Result<$ret> {
self.session()
.$name($($arg),*)
self.session.$name($($arg),*)
.map_err(|e| SshError::$err_variant(self.id(), e))
}
}
}

impl<'a> BorrowedSession<'a> {
impl SshSession {
inherit_method!(connect, (), Connect);
inherit_method!(get_server_public_key, SshKey, GetServerPublicKey);
inherit_method!(get_server_banner, String, GetServerBanner);
Expand Down
21 changes: 11 additions & 10 deletions rust/src/nasl/builtin/ssh/libssh/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@

use std::collections::{HashMap, HashSet};

use tokio::sync::Mutex;
use tokio::sync::{Mutex, MutexGuard};

use crate::nasl::builtin::ssh::SessionId;

use super::super::error::{Result, SshError};
use super::session::{BorrowedSession, SshSession};
use super::session::SshSession;

#[derive(Default)]
pub struct Ssh {
Expand All @@ -20,15 +20,16 @@ pub struct Ssh {
sessions: HashMap<SessionId, Mutex<SshSession>>,
}

type BorrowedSession<'a> = MutexGuard<'a, SshSession>;

impl Ssh {
pub async fn get_by_id(&self, id: SessionId) -> Result<BorrowedSession> {
Ok(BorrowedSession::new(
self.sessions
.get(&id)
.ok_or_else(|| SshError::InvalidSessionId(id))?
.lock()
.await,
))
Ok(self
.sessions
.get(&id)
.ok_or_else(|| SshError::InvalidSessionId(id))?
.lock()
.await)
}

/// Return the next available session ID
Expand Down Expand Up @@ -75,7 +76,7 @@ impl Ssh {
let id = self.next_session_id()?;
let session = Mutex::new(SshSession::new(id)?);
{
let mut borrowed_session = BorrowedSession::new(session.lock().await);
let mut borrowed_session = session.lock().await;
if let Err(e) = f(&mut borrowed_session) {
borrowed_session.disconnect()?;
return Err(e);
Expand Down
3 changes: 3 additions & 0 deletions rust/src/nasl/builtin/ssh/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
mod error;
mod libssh;

#[cfg(test)]
mod tests;

type SessionId = i32;
pub use self::libssh::Ssh;
50 changes: 50 additions & 0 deletions rust/src/nasl/builtin/ssh/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
mod server;

use std::sync::Arc;
use std::time::Duration;

use russh::server::Server as _;
use server::TestServer;

use crate::nasl::test_prelude::TestBuilder;
use crate::nasl::NoOpLoader;
use crate::storage::DefaultDispatcher;

const PORT: u16 = 2223;

#[tokio::test]
async fn ssh_connect() {
run_test(|mut t| {
t.ok(format!("id = ssh_connect(port:{});", PORT), 9000);
t.ok(format!("id = ssh_connect(port:{});", PORT), 9001);
})
.await
}

async fn run_test(f: impl Fn(TestBuilder<NoOpLoader, DefaultDispatcher>) -> () + Send + 'static) {
let server = tokio::time::timeout(Duration::from_millis(2000), run_server());
let client = tokio::task::spawn_blocking(move || {
std::thread::sleep(Duration::from_millis(1000));
let t = TestBuilder::default();
f(t)
});
let (ser, res) = futures::join!(server, client);
assert!(ser.is_err());
res.unwrap()
}

async fn run_server() {
let config = russh::server::Config {
inactivity_timeout: Some(Duration::from_secs(3600)),
auth_rejection_time: Duration::from_secs(3),
auth_rejection_time_initial: Some(Duration::from_secs(0)),
keys: vec![russh_keys::key::KeyPair::generate_ed25519().unwrap()],
..Default::default()
};
let config = Arc::new(config);
let mut server = TestServer::default();
server
.run_on_address(config, ("0.0.0.0", PORT))
.await
.unwrap();
}
101 changes: 101 additions & 0 deletions rust/src/nasl/builtin/ssh/tests/server.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
use std::collections::HashMap;
use std::sync::Arc;

use async_trait::async_trait;
use russh::keys::*;
use russh::server::{Msg, Session};
use russh::*;
use tokio::sync::Mutex;

#[derive(Clone, Default)]
pub struct TestServer {
clients: Arc<Mutex<HashMap<(usize, ChannelId), russh::server::Handle>>>,
id: usize,
}

impl TestServer {
async fn post(&mut self, data: CryptoVec) {
let mut clients = self.clients.lock().await;
for ((id, channel), ref mut s) in clients.iter_mut() {
if *id != self.id {
let _ = s.data(*channel, data.clone()).await;
}
}
}
}

impl server::Server for TestServer {
type Handler = Self;

fn new_client(&mut self, _: Option<std::net::SocketAddr>) -> Self {
let s = self.clone();
self.id += 1;
s
}

fn handle_session_error(&mut self, _error: <Self::Handler as russh::server::Handler>::Error) {
eprintln!("Session error: {:#?}", _error);
}
}

#[async_trait]
impl server::Handler for TestServer {
type Error = russh::Error;

async fn channel_open_session(
&mut self,
channel: Channel<Msg>,
session: &mut Session,
) -> Result<bool, Self::Error> {
{
let mut clients = self.clients.lock().await;
clients.insert((self.id, channel.id()), session.handle());
}
Ok(true)
}

async fn auth_publickey(
&mut self,
_: &str,
_: &key::PublicKey,
) -> Result<server::Auth, Self::Error> {
Ok(server::Auth::Accept)
}

async fn data(
&mut self,
channel: ChannelId,
data: &[u8],
session: &mut Session,
) -> Result<(), Self::Error> {
// Sending Ctrl+C ends the session and disconnects the client
if data == [3] {
return Err(russh::Error::Disconnect);
}

let data = CryptoVec::from(format!("Got data: {}\r\n", String::from_utf8_lossy(data)));
self.post(data.clone()).await;
session.data(channel, data);
Ok(())
}

async fn tcpip_forward(
&mut self,
address: &str,
port: &mut u32,
session: &mut Session,
) -> Result<bool, Self::Error> {
let handle = session.handle();
let address = address.to_string();
let port = *port;
tokio::spawn(async move {
let channel = handle
.channel_open_forwarded_tcpip(address, port, "1.2.3.4", 1234)
.await
.unwrap();
let _ = channel.data(&b"Hello from a forwarded port"[..]).await;
let _ = channel.eof().await;
});
Ok(true)
}
}
10 changes: 5 additions & 5 deletions rust/src/nasl/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,8 @@ where
S: Storage,
{
#[track_caller]
fn add_line(&mut self, line: &str, val: TestResult) -> &mut Self {
self.lines.push(line.to_string());
fn add_line(&mut self, line: impl Into<String>, val: TestResult) -> &mut Self {
self.lines.push(line.into());
self.results.push(val.into());
self
}
Expand All @@ -157,7 +157,7 @@ where
/// t.ok("x = 3;", 3);
/// ```
#[track_caller]
pub fn ok(&mut self, line: &str, val: impl ToNaslResult) -> &mut Self {
pub fn ok(&mut self, line: impl Into<String>, val: impl ToNaslResult) -> &mut Self {
self.add_line(line, TestResult::Ok(val.to_nasl_result().unwrap()))
}

Expand All @@ -174,15 +174,15 @@ where
#[track_caller]
pub fn check(
&mut self,
line: &str,
line: impl Into<String>,
f: impl Fn(NaslResult) -> bool + 'static + Clone,
) -> &mut Self {
self.add_line(line, TestResult::GenericCheck(Box::new(f)))
}

/// Run a `line` of NASL code without checking its result.
#[track_caller]
pub fn run(&mut self, line: &str) -> &mut Self {
pub fn run(&mut self, line: impl Into<String>) -> &mut Self {
self.add_line(line, TestResult::None)
}

Expand Down

0 comments on commit 2b63150

Please sign in to comment.