use crate::config::{manager::ConfigManager, OPNsenseShell}; use crate::error::Error; use async_trait::async_trait; use log::{info, warn}; use russh_keys::key::KeyPair; use sha2::Digest; use std::sync::Arc; #[derive(Debug)] pub enum SshCredentials { SshKey { username: String, key: Arc }, Password { username: String, password: String }, } #[derive(Debug)] pub struct SshConfigManager { opnsense_shell: Arc, } impl SshConfigManager { pub fn new(opnsense_shell: Arc) -> Self { Self { opnsense_shell } } } impl SshConfigManager { async fn backup_config_remote(&self) -> Result { let ts = chrono::Utc::now(); let backup_filename = format!("config-{}-harmony.xml", ts.format("%s%.3f")); self.opnsense_shell .exec(&format!( "cp /conf/config.xml /conf/backup/{}", backup_filename )) .await } async fn copy_to_live_config(&self, new_config_path: &str) -> Result { info!("Overwriting OPNSense /conf/config.xml with {new_config_path}"); self.opnsense_shell .exec(&format!("cp {new_config_path} /conf/config.xml")) .await } async fn reload_all_services(&self) -> Result { info!("Reloading all opnsense services"); self.opnsense_shell .exec("configctl service reload all") .await } } #[async_trait] impl ConfigManager for SshConfigManager { async fn load_as_str(&self) -> Result { self.opnsense_shell.exec("cat /conf/config.xml").await } async fn save_config(&self, content: &str, hash: &str) -> Result<(), Error> { let current_content = self.load_as_str().await?; if !check_hash(¤t_content, hash) { warn!("OPNSense config file changed since loading it! Hash when loading : {hash}"); // return Err(Error::Config(format!( // "OPNSense config file changed since loading it! Hash when loading : {hash}" // ))); } let temp_filename = self .opnsense_shell .write_content_to_temp_file(content) .await?; self.backup_config_remote().await?; self.copy_to_live_config(&temp_filename).await?; Ok(()) } async fn apply_new_config(&self, content: &str, hash: &str) -> Result<(), Error> { self.save_config(content, &hash).await?; self.reload_all_services().await?; Ok(()) } } pub fn get_hash(content: &str) -> String { let mut hasher = sha2::Sha256::new(); hasher.update(content.as_bytes()); let hash_bytes = hasher.finalize(); let hash_string = format!("{:x}", hash_bytes); info!("Loaded OPNSense config.xml with hash {hash_string:?}"); hash_string } pub fn check_hash(content: &str, source_hash: &str) -> bool { get_hash(content) == source_hash }