All checks were successful
Run Check Script / check (pull_request) Successful in 1m5s
* Expose a high-level `brocade::init()` function to connect to a Brocade switch and automatically pick the best implementation based on its OS and version * Implement a client for Brocade switches running on Network Operating System (NOS) * Implement a client for older Brocade switches running on FastIron (partial implementation) The architecture for the library is based on 3 layers: 1. The `BrocadeClient` trait to describe the available capabilities to interact with a Brocade switch. It is partly opinionated in order to offer higher level features to group multiple commands into a single function (e.g. create a port channel). Its implementations are basically just the commands to run on the switch and the functions to parse the output. 2. The `BrocadeShell` struct to make it easier to authenticate, send commands, and interact with the switch. 3. The `ssh` module to actually connect to the switch over SSH and execute the commands. With time, we will add support for more Brocade switches and their various OS/versions. If needed, shared behavior could be extracted into a separate module to make it easier to add new implementations.
114 lines
2.8 KiB
Rust
114 lines
2.8 KiB
Rust
use std::borrow::Cow;
|
|
use std::sync::Arc;
|
|
|
|
use async_trait::async_trait;
|
|
use russh::client::Handler;
|
|
use russh::kex::DH_G1_SHA1;
|
|
use russh::kex::ECDH_SHA2_NISTP256;
|
|
use russh_keys::key::SSH_RSA;
|
|
|
|
use super::BrocadeOptions;
|
|
use super::Error;
|
|
|
|
#[derive(Default, Clone, Debug)]
|
|
pub struct SshOptions {
|
|
pub preferred_algorithms: russh::Preferred,
|
|
}
|
|
|
|
impl SshOptions {
|
|
fn ecdhsa_sha2_nistp256() -> Self {
|
|
Self {
|
|
preferred_algorithms: russh::Preferred {
|
|
kex: Cow::Borrowed(&[ECDH_SHA2_NISTP256]),
|
|
key: Cow::Borrowed(&[SSH_RSA]),
|
|
..Default::default()
|
|
},
|
|
}
|
|
}
|
|
|
|
fn legacy() -> Self {
|
|
Self {
|
|
preferred_algorithms: russh::Preferred {
|
|
kex: Cow::Borrowed(&[DH_G1_SHA1]),
|
|
key: Cow::Borrowed(&[SSH_RSA]),
|
|
..Default::default()
|
|
},
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct Client;
|
|
|
|
#[async_trait]
|
|
impl Handler for Client {
|
|
type Error = Error;
|
|
|
|
async fn check_server_key(
|
|
&mut self,
|
|
_server_public_key: &russh_keys::key::PublicKey,
|
|
) -> Result<bool, Self::Error> {
|
|
Ok(true)
|
|
}
|
|
}
|
|
|
|
pub async fn try_init_client(
|
|
username: &str,
|
|
password: &str,
|
|
ip: &std::net::IpAddr,
|
|
base_options: BrocadeOptions,
|
|
) -> Result<BrocadeOptions, Error> {
|
|
let ssh_options = vec![
|
|
SshOptions::default(),
|
|
SshOptions::ecdhsa_sha2_nistp256(),
|
|
SshOptions::legacy(),
|
|
];
|
|
|
|
for ssh in ssh_options {
|
|
let opts = BrocadeOptions {
|
|
ssh,
|
|
..base_options.clone()
|
|
};
|
|
let client = create_client(*ip, 22, username, password, &opts).await;
|
|
|
|
match client {
|
|
Ok(_) => {
|
|
return Ok(opts);
|
|
}
|
|
Err(e) => match e {
|
|
Error::NetworkError(e) => {
|
|
if e.contains("No common key exchange algorithm") {
|
|
continue;
|
|
} else {
|
|
return Err(Error::NetworkError(e));
|
|
}
|
|
}
|
|
_ => return Err(e),
|
|
},
|
|
}
|
|
}
|
|
|
|
Err(Error::NetworkError(
|
|
"Could not establish ssh connection: wrong key exchange algorithm)".to_string(),
|
|
))
|
|
}
|
|
|
|
pub async fn create_client(
|
|
ip: std::net::IpAddr,
|
|
port: u16,
|
|
username: &str,
|
|
password: &str,
|
|
options: &BrocadeOptions,
|
|
) -> Result<russh::client::Handle<Client>, Error> {
|
|
let config = russh::client::Config {
|
|
preferred: options.ssh.preferred_algorithms.clone(),
|
|
..Default::default()
|
|
};
|
|
let mut client = russh::client::connect(Arc::new(config), (ip, port), Client {}).await?;
|
|
if !client.authenticate_password(username, password).await? {
|
|
return Err(Error::AuthenticationError(
|
|
"ssh authentication failed".to_string(),
|
|
));
|
|
}
|
|
Ok(client)
|
|
}
|