diff --git a/harmony-rs/Cargo.lock b/harmony-rs/Cargo.lock index 6831b38..22e902d 100644 --- a/harmony-rs/Cargo.lock +++ b/harmony-rs/Cargo.lock @@ -895,6 +895,7 @@ dependencies = [ "env_logger", "libredfish", "log", + "opnsense-config", "reqwest", "russh", "rust-ipmi", @@ -902,7 +903,6 @@ dependencies = [ "serde", "serde_json", "tokio", - "xml_dom", ] [[package]] @@ -1627,15 +1627,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "quick-xml" -version = "0.36.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7649a7b4df05aed9ea7ec6f628c67c9953a43869b8bc50929569b2999d443fe" -dependencies = [ - "memchr", -] - [[package]] name = "quote" version = "1.0.37" @@ -2430,21 +2421,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", - "tracing-attributes", "tracing-core", ] -[[package]] -name = "tracing-attributes" -version = "0.1.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.77", -] - [[package]] name = "tracing-core" version = "0.1.32" @@ -2822,18 +2801,6 @@ version = "0.8.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af4e2e2f7cba5a093896c1e150fbfe177d1883e7448200efb81d40b9d339ef26" -[[package]] -name = "xml_dom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836bd445caf6b9e969199f2a9667d58b433b286ddb515764303ab75a6d17e51f" -dependencies = [ - "log", - "quick-xml", - "regex", - "tracing", -] - [[package]] name = "yansi" version = "1.0.1" diff --git a/harmony-rs/Cargo.toml b/harmony-rs/Cargo.toml index bd5e6b8..20aba71 100644 --- a/harmony-rs/Cargo.toml +++ b/harmony-rs/Cargo.toml @@ -18,6 +18,8 @@ derive-new = "0.7.0" async-trait = "0.1.82" tokio = { version = "1.40.0", features = ["io-std"] } cidr = "0.2.3" -xml_dom = "0.2.8" russh = "0.45.0" russh-keys = "0.45.0" + +#[workspace.target.x86_64-unknown-linux-gnu] +#rustflags = ["-C", "link-arg=-fuse-ld=mold"] diff --git a/harmony-rs/harmony/Cargo.toml b/harmony-rs/harmony/Cargo.toml index 5917bdf..8029cf1 100644 --- a/harmony-rs/harmony/Cargo.toml +++ b/harmony-rs/harmony/Cargo.toml @@ -17,4 +17,4 @@ log = { workspace = true } env_logger = { workspace = true } async-trait = { workspace = true } cidr = { workspace = true } -xml_dom = { workspace = true } +opnsense-config = { path = "../opnsense-config" } diff --git a/harmony-rs/harmony/src/domain/topology/mod.rs b/harmony-rs/harmony/src/domain/topology/mod.rs index bd4a172..bfb9c96 100644 --- a/harmony-rs/harmony/src/domain/topology/mod.rs +++ b/harmony-rs/harmony/src/domain/topology/mod.rs @@ -22,6 +22,7 @@ pub struct HAClusterTopology { } pub type IpAddress = IpAddr; + /// Represents a logical member of a cluster that provides one or more services. /// /// A LogicalHost can represent various roles within the infrastructure, such as: diff --git a/harmony-rs/harmony/src/domain/topology/network.rs b/harmony-rs/harmony/src/domain/topology/network.rs index 8096af9..be55f37 100644 --- a/harmony-rs/harmony/src/domain/topology/network.rs +++ b/harmony-rs/harmony/src/domain/topology/network.rs @@ -1,3 +1,5 @@ +use std::net::Ipv4Addr; + use async_trait::async_trait; use crate::executors::ExecutorError; @@ -8,12 +10,15 @@ use super::{IpAddress, LogicalHost}; pub struct DHCPStaticEntry { pub name: String, pub mac: MacAddress, - pub ip: IpAddress, + pub ip: Ipv4Addr, } impl std::fmt::Display for DHCPStaticEntry { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_fmt(format_args!("DHCPStaticEntry : name {}, mac {}, ip {}", self.name, self.mac, self.ip)) + f.write_fmt(format_args!( + "DHCPStaticEntry : name {}, mac {}, ip {}", + self.name, self.mac, self.ip + )) } } @@ -57,7 +62,11 @@ pub trait DnsServer: Send + Sync { record_type: DnsRecordType, value: &str, ) -> Result<(), ExecutorError>; - fn remove_record(&mut self, name: &str, record_type: DnsRecordType) -> Result<(), ExecutorError>; + fn remove_record( + &mut self, + name: &str, + record_type: DnsRecordType, + ) -> Result<(), ExecutorError>; fn list_records(&self) -> Vec; fn get_ip(&self) -> IpAddress; fn get_host(&self) -> LogicalHost; @@ -98,11 +107,21 @@ pub struct MacAddress(pub [u8; 6]); // MacAddress::from!("00:90:7f:df:2c:23"), impl MacAddress { + #[cfg(test)] pub fn dummy() -> Self { Self([0, 0, 0, 0, 0, 0]) } } +impl From<&MacAddress> for String { + fn from(value: &MacAddress) -> Self { + format!( + "{}:{}:{}:{}:{}:{}", + value.0[0], value.0[1], value.0[2], value.0[3], value.0[4], value.0[5] + ) + } +} + impl std::fmt::Display for MacAddress { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.write_fmt(format_args!( diff --git a/harmony-rs/harmony/src/infra/opnsense/config.rs b/harmony-rs/harmony/src/infra/opnsense/config.rs deleted file mode 100644 index 309e59d..0000000 --- a/harmony-rs/harmony/src/infra/opnsense/config.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::executors::ExecutorError; - -pub struct OPNSenseXmlConfigEditor; - -impl OPNSenseXmlConfigEditor { - pub(crate) async fn add(vec: Vec<&str>, xml_entry: &str) -> Result<(), ExecutorError>{ - todo!() - } -} - -#[cfg(test)] -mod test { - use std::fs; - use std::io::{BufReader, Read}; - use std::process::Command; - - use xml_dom::parser::read_xml; - - // #[test] - // fn should_not_alter_config() { - // let path = "./private_repos/affilium_mcd/private/config.xml"; - // // TODO - // // Load file to string - // // Parse with minidom (ex: `let root: Element = file_str.parse().unwrap()`) - // // save file with suffix name - // // Verify that file is still identical with md5sum after removing indentation - // } - - #[test] - fn should_not_alter_config() { - let path = "/home/jeangab/work/nationtech/harmony/harmony-rs/affilium_mcd/private/config.xml"; - let output_path = format!("{}.test_output", path); - - // Load file to string - let file_str = fs::read_to_string(path).expect("Failed to read file"); - - // Parse with minidom - let root = read_xml(&file_str).unwrap(); - - assert_eq!(&root.to_string(), &file_str); - } - -} diff --git a/harmony-rs/harmony/src/infra/opnsense/mod.rs b/harmony-rs/harmony/src/infra/opnsense/mod.rs index fd7bd6c..66b00e6 100644 --- a/harmony-rs/harmony/src/infra/opnsense/mod.rs +++ b/harmony-rs/harmony/src/infra/opnsense/mod.rs @@ -1,18 +1,21 @@ mod management; -mod config; +use std::sync::{Arc, Mutex, RwLock, RwLockWriteGuard}; + use async_trait::async_trait; +use log::debug; pub use management::*; use crate::{ - executors::ExecutorError, infra::opnsense::config::OPNSenseXmlConfigEditor, topology::{ + executors::ExecutorError, + topology::{ Backend, DHCPStaticEntry, DhcpServer, DnsServer, Firewall, FirewallRule, Frontend, IpAddress, LoadBalancer, LogicalHost, - } + }, }; -use derive_new::new; -#[derive(new, Clone)] +#[derive(Clone)] pub struct OPNSenseFirewall { + opnsense_config: Arc>, host: LogicalHost, cluster_nic_name: String, } @@ -22,6 +25,20 @@ impl OPNSenseFirewall { self.host.ip } + pub async fn new( + host: LogicalHost, + cluster_nic_name: &str, + username: &str, + password: &str, + ) -> Self { + Self { + opnsense_config: Arc::new(RwLock::new( + opnsense_config::Config::from_credentials(host.ip, username, password).await, + )), + host, + cluster_nic_name: cluster_nic_name.into(), + } + } } impl Firewall for OPNSenseFirewall { @@ -81,27 +98,17 @@ impl LoadBalancer for OPNSenseFirewall { #[async_trait] impl DhcpServer for OPNSenseFirewall { async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError> { - let mac = &entry.mac; - let name = &entry.name; - let ip = &entry.ip; + let mac: String = String::from(&entry.mac); - let xml_entry = format!( - " - {mac} - {ip} - {name} - {name} - - - - " - ); - // XML Path : - OPNSenseXmlConfigEditor::add( - vec!["opnsense", "dhcpd", &self.cluster_nic_name, "staticmap"], - &xml_entry, - ).await?; - todo!("Register {:?}", entry) + { + let mut writable_opnsense = self.opnsense_config.write().unwrap(); + writable_opnsense + .dhcp() + .add_static_mapping(&mac, entry.ip, &entry.name).unwrap(); + } + + debug!("Registered {:?}", entry); + Ok(()) } async fn remove_static_mapping( @@ -122,6 +129,7 @@ impl DhcpServer for OPNSenseFirewall { self.host.clone() } } + impl DnsServer for OPNSenseFirewall { fn add_record( &mut self, diff --git a/harmony-rs/harmony/src/modules/dhcp.rs b/harmony-rs/harmony/src/modules/dhcp.rs index 246576b..37e49cb 100644 --- a/harmony-rs/harmony/src/modules/dhcp.rs +++ b/harmony-rs/harmony/src/modules/dhcp.rs @@ -1,4 +1,4 @@ -use std::sync::Arc; +use std::{net::Ipv4Addr, sync::Arc}; use async_trait::async_trait; use derive_new::new; @@ -120,10 +120,17 @@ impl Interpret for DhcpInterpret { .score .host_binding .iter() - .map(|binding| DHCPStaticEntry { - name: binding.logical_host.name.clone(), - mac: binding.physical_host.cluster_mac(), - ip: binding.logical_host.ip, + .map(|binding| { + let ip = match binding.logical_host.ip { + std::net::IpAddr::V4(ipv4) => ipv4, + std::net::IpAddr::V6(_) => unimplemented!("DHCPStaticEntry only supports ipv4 at the moment"), + }; + + DHCPStaticEntry { + name: binding.logical_host.name.clone(), + mac: binding.physical_host.cluster_mac(), + ip, + } }) .collect(); info!("DHCPStaticEntry : {:?}", dhcp_entries); @@ -136,6 +143,7 @@ impl Interpret for DhcpInterpret { Err(_) => todo!(), } } + todo!("Configure DHCPServer"); Ok(Outcome::new( diff --git a/harmony-rs/opnsense-config-xml/Cargo.toml b/harmony-rs/opnsense-config-xml/Cargo.toml index a193f0c..3b28dcb 100644 --- a/harmony-rs/opnsense-config-xml/Cargo.toml +++ b/harmony-rs/opnsense-config-xml/Cargo.toml @@ -20,3 +20,6 @@ tokio = { workspace = true } [dev-dependencies] pretty_assertions = "1.4.1" + +[target.x86_64-unknown-linux-gnu] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] diff --git a/harmony-rs/opnsense-config/src/config/config.rs b/harmony-rs/opnsense-config/src/config/config.rs index 29dbd29..b38fa29 100644 --- a/harmony-rs/opnsense-config/src/config/config.rs +++ b/harmony-rs/opnsense-config/src/config/config.rs @@ -1,8 +1,9 @@ -use std::sync::Arc; +use std::{net::Ipv4Addr, sync::Arc, time::Duration}; -use crate::{error::Error, modules::dhcp::DhcpConfig}; +use crate::{config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, error::Error, modules::dhcp::DhcpConfig}; use log::trace; use opnsense_config_xml::OPNsense; +use russh::client; use super::{ConfigManager, OPNsenseShell}; @@ -39,6 +40,27 @@ impl Config { .apply_new_config(&self.opnsense.to_xml()) .await } + + pub async fn from_credentials(ipaddr: std::net::IpAddr, username: &str, password: &str) -> Self { + let config = Arc::new(client::Config { + inactivity_timeout: Some(Duration::from_secs(5)), + ..<_>::default() + }); + + let credentials = SshCredentials::Password { + username: String::from(username), + password: String::from(password), + }; + + let shell = Arc::new(SshOPNSenseShell::new( + (ipaddr, 22), + credentials, + config, + )); + let manager = Arc::new(SshConfigManager::new(shell.clone())); + + Config::new(manager, shell).await.unwrap() + } } #[cfg(test)] diff --git a/harmony-rs/opnsense-config/src/config/manager/mod.rs b/harmony-rs/opnsense-config/src/config/manager/mod.rs index 4ac2142..42f95ca 100644 --- a/harmony-rs/opnsense-config/src/config/manager/mod.rs +++ b/harmony-rs/opnsense-config/src/config/manager/mod.rs @@ -7,7 +7,7 @@ pub use local_file::*; use crate::Error; #[async_trait] -pub trait ConfigManager: std::fmt::Debug { +pub trait ConfigManager: std::fmt::Debug + Send + Sync { async fn load_as_str(&self) -> Result; async fn apply_new_config(&self, content: &str) -> Result<(), Error>; } diff --git a/harmony-rs/opnsense-config/src/config/shell/ssh.rs b/harmony-rs/opnsense-config/src/config/shell/ssh.rs index 62ff391..453231c 100644 --- a/harmony-rs/opnsense-config/src/config/shell/ssh.rs +++ b/harmony-rs/opnsense-config/src/config/shell/ssh.rs @@ -1,5 +1,5 @@ use std::{ - net::Ipv4Addr, + net::IpAddr, sync::Arc, time::{SystemTime, UNIX_EPOCH}, }; @@ -20,7 +20,7 @@ use super::OPNsenseShell; #[derive(Debug)] pub struct SshOPNSenseShell { - host: (Ipv4Addr, u16), + host: (IpAddr, u16), credentials: SshCredentials, ssh_config: Arc, } @@ -78,11 +78,7 @@ impl SshOPNSenseShell { wait_for_completion(&mut channel).await } - pub fn new( - host: (Ipv4Addr, u16), - credentials: SshCredentials, - ssh_config: Arc, - ) -> Self { + pub fn new(host: (IpAddr, u16), credentials: SshCredentials, ssh_config: Arc) -> Self { Self { host, credentials, diff --git a/harmony-rs/opnsense-config/src/lib.rs b/harmony-rs/opnsense-config/src/lib.rs index 00a411b..59a378e 100644 --- a/harmony-rs/opnsense-config/src/lib.rs +++ b/harmony-rs/opnsense-config/src/lib.rs @@ -6,15 +6,10 @@ pub use config::Config; pub use error::Error; #[cfg(test)] mod test { - use config::SshConfigManager; use opnsense_config_xml::StaticMap; - use russh::client; - use std::{net::Ipv4Addr, sync::Arc, time::Duration}; + use std::net::Ipv4Addr; - use crate::{ - config::{self, SshCredentials, SshOPNSenseShell}, - Config, - }; + use crate::Config; use pretty_assertions::assert_eq; #[tokio::test] @@ -38,23 +33,7 @@ mod test { } async fn initialize_config() -> Config { - let config = Arc::new(client::Config { - inactivity_timeout: Some(Duration::from_secs(5)), - ..<_>::default() - }); - - let credentials = SshCredentials::Password { - username: String::from("root"), - password: String::from("opnsense"), - }; - - let shell = Arc::new(SshOPNSenseShell::new( - (Ipv4Addr::new(192, 168, 5, 229), 22), - credentials, - config, - )); - let manager = Arc::new(SshConfigManager::new(shell.clone())); - Config::new(manager, shell).await.unwrap() + Config::from_credentials(Ipv4Addr::new(192, 168, 5, 229), "root", "opnsense").await } async fn get_static_mappings() -> Vec {