add execution mode to run brocade commands in privileged mode (enable)
This commit is contained in:
		
							parent
							
								
									1199564122
								
							
						
					
					
						commit
						731dc5f404
					
				| @ -7,7 +7,7 @@ use std::{ | ||||
| 
 | ||||
| use async_trait::async_trait; | ||||
| use harmony_types::net::{IpAddress, MacAddress}; | ||||
| use log::{debug, info}; | ||||
| use log::{debug, info, trace}; | ||||
| use russh::{ | ||||
|     ChannelMsg, | ||||
|     client::{Handle, Handler}, | ||||
| @ -79,6 +79,11 @@ impl Default for SshOptions { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| enum ExecutionMode { | ||||
|     Regular, | ||||
|     Privileged, | ||||
| } | ||||
| 
 | ||||
| impl BrocadeClient { | ||||
|     pub async fn init( | ||||
|         ip_addresses: &[IpAddress], | ||||
| @ -115,7 +120,9 @@ impl BrocadeClient { | ||||
|     } | ||||
| 
 | ||||
|     pub async fn show_mac_address_table(&self) -> Result<Vec<MacAddressEntry>, Error> { | ||||
|         let output = self.run_command("show mac-address").await?; | ||||
|         let output = self | ||||
|             .run_command("show mac-address", ExecutionMode::Regular) | ||||
|             .await?; | ||||
| 
 | ||||
|         output | ||||
|             .lines() | ||||
| @ -130,14 +137,15 @@ impl BrocadeClient { | ||||
|         let channel_id = self.find_available_channel_id().await?; | ||||
|         let commands = self.build_port_channel_commands(channel_id, ports); | ||||
| 
 | ||||
|         self.run_commands(commands).await?; | ||||
|         self.run_commands(commands, ExecutionMode::Privileged) | ||||
|             .await?; | ||||
|         Ok(channel_id) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn find_available_channel_id(&self) -> Result<u8, Error> { | ||||
|         debug!("[Brocade] Finding next available channel id..."); | ||||
| 
 | ||||
|         let output = self.run_command("show lag").await?; | ||||
|         let output = self.run_command("show lag", ExecutionMode::Regular).await?; | ||||
|         let mut used_ids: Vec<u8> = output | ||||
|             .lines() | ||||
|             .filter_map(|line| { | ||||
| @ -158,14 +166,13 @@ impl BrocadeClient { | ||||
|         Ok(next_id) | ||||
|     } | ||||
| 
 | ||||
|     async fn run_command(&self, command: &str) -> Result<String, Error> { | ||||
|     async fn run_command(&self, command: &str, mode: ExecutionMode) -> Result<String, Error> { | ||||
|         if self.should_skip_command(command) { | ||||
|             return Ok(String::new()); | ||||
|         } | ||||
| 
 | ||||
|         let mut channel = self.client.channel_open_session().await?; | ||||
|         self.setup_channel(&mut channel).await?; | ||||
|         self.wait_for_shell_ready(&mut channel).await?; | ||||
|         self.setup_channel(&mut channel, mode).await?; | ||||
| 
 | ||||
|         let output = self | ||||
|             .execute_command_in_session(&mut channel, command) | ||||
| @ -178,14 +185,13 @@ impl BrocadeClient { | ||||
|         Ok(cleaned) | ||||
|     } | ||||
| 
 | ||||
|     async fn run_commands(&self, commands: Vec<String>) -> Result<(), Error> { | ||||
|     async fn run_commands(&self, commands: Vec<String>, mode: ExecutionMode) -> Result<(), Error> { | ||||
|         if commands.is_empty() { | ||||
|             return Ok(()); | ||||
|         } | ||||
| 
 | ||||
|         let mut channel = self.client.channel_open_session().await?; | ||||
|         self.setup_channel(&mut channel).await?; | ||||
|         self.wait_for_shell_ready(&mut channel).await?; | ||||
|         self.setup_channel(&mut channel, mode).await?; | ||||
| 
 | ||||
|         for command in commands { | ||||
|             if self.should_skip_command(&command) { | ||||
| @ -210,13 +216,23 @@ impl BrocadeClient { | ||||
|     async fn setup_channel( | ||||
|         &self, | ||||
|         channel: &mut russh::Channel<russh::client::Msg>, | ||||
|         mode: ExecutionMode, | ||||
|     ) -> Result<(), Error> { | ||||
|         // Setup PTY and shell
 | ||||
|         channel | ||||
|             .request_pty(false, "vt100", 80, 24, 0, 0, &[]) | ||||
|             .await?; | ||||
|         channel.request_shell(false).await?; | ||||
|         Ok(()) | ||||
| 
 | ||||
|         self.wait_for_shell_ready(channel).await?; | ||||
| 
 | ||||
|         match mode { | ||||
|             ExecutionMode::Regular => Ok(()), | ||||
|             ExecutionMode::Privileged => { | ||||
|                 debug!("[Brocade] Attempting privilege escalation (enable mode)..."); | ||||
|                 self.try_elevate_session(channel).await | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async fn execute_command_in_session( | ||||
| @ -235,6 +251,63 @@ impl BrocadeClient { | ||||
|             .map_err(|_| Error::UnexpectedError("Invalid UTF-8 in command output".to_string())) | ||||
|     } | ||||
| 
 | ||||
|     async fn try_elevate_session( | ||||
|         &self, | ||||
|         channel: &mut russh::Channel<russh::client::Msg>, | ||||
|     ) -> Result<(), Error> { | ||||
|         channel.data(&b"enable\n"[..]).await?; | ||||
|         let start = Instant::now(); | ||||
|         let mut buffer = Vec::new(); | ||||
| 
 | ||||
|         while start.elapsed() < self.options.timeouts.shell_ready { | ||||
|             match timeout(self.options.timeouts.message_wait, channel.wait()).await { | ||||
|                 Ok(Some(ChannelMsg::Data { data })) => { | ||||
|                     buffer.extend_from_slice(&data); | ||||
|                     let output = String::from_utf8_lossy(&buffer); | ||||
| 
 | ||||
|                     if output.ends_with('#') { | ||||
|                         debug!("[Brocade] Privileged mode established"); | ||||
|                         return Ok(()); | ||||
|                     } | ||||
| 
 | ||||
|                     if output.contains("User Name:") { | ||||
|                         channel | ||||
|                             .data(format!("{}\n", self.elevated_user.username).as_bytes()) | ||||
|                             .await?; | ||||
|                         buffer.clear(); | ||||
|                     } else if output.contains("Password:") { | ||||
|                         // Note: Brocade might not echo the password field
 | ||||
|                         channel | ||||
|                             .data(format!("{}\n", self.elevated_user.password).as_bytes()) | ||||
|                             .await?; | ||||
|                         buffer.clear(); | ||||
|                     } else if output.contains('>') { | ||||
|                         // Back to user mode, something failed (e.g., wrong password)
 | ||||
|                         return Err(Error::AuthenticationError( | ||||
|                             "Enable authentication failed or access denied.".to_string(), | ||||
|                         )); | ||||
|                     } | ||||
|                 } | ||||
|                 Ok(Some(_)) => continue, | ||||
|                 Ok(None) => break, | ||||
|                 Err(_) => continue, | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Check final state if timeout was reached
 | ||||
|         let output = String::from_utf8_lossy(&buffer); | ||||
|         let elevated = output.ends_with('#'); | ||||
|         match elevated { | ||||
|             true => { | ||||
|                 debug!("[Brocade] Privileged mode established"); | ||||
|                 Ok(()) | ||||
|             } | ||||
|             false => Err(Error::AuthenticationError(format!( | ||||
|                 "Enable authentication failed for an unknown reason. Output was:\n{output}", | ||||
|             ))), | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     async fn wait_for_shell_ready( | ||||
|         &self, | ||||
|         channel: &mut russh::Channel<russh::client::Msg>, | ||||
| @ -247,7 +320,7 @@ impl BrocadeClient { | ||||
|                 Ok(Some(ChannelMsg::Data { data })) => { | ||||
|                     buffer.extend_from_slice(&data); | ||||
|                     let output = String::from_utf8_lossy(&buffer); | ||||
|                     if output.contains('>') || output.contains('#') { | ||||
|                     if output.ends_with('>') || output.ends_with('#') { | ||||
|                         debug!("[Brocade] Shell ready: {}", output.trim()); | ||||
|                         return Ok(()); | ||||
|                     } | ||||
| @ -351,7 +424,7 @@ impl BrocadeClient { | ||||
|     } | ||||
| 
 | ||||
|     fn clean_brocade_output(&self, raw_output: &str, command: &str) -> String { | ||||
|         debug!("[Brocade] Received command output:\n{raw_output}"); | ||||
|         trace!("[Brocade] Received raw output:\n{raw_output}"); | ||||
| 
 | ||||
|         let lines: Vec<&str> = raw_output.lines().collect(); | ||||
|         let mut cleaned_lines = Vec::new(); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user