Integrated opnsense-config API in harmony, pretty easy, pretty happy. very much encouraging

This commit is contained in:
jeangab 2024-11-26 15:58:42 -05:00
parent d30e909b83
commit 9c18c8be6d
13 changed files with 108 additions and 146 deletions

35
harmony-rs/Cargo.lock generated
View File

@ -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"

View File

@ -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"]

View File

@ -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" }

View File

@ -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:

View File

@ -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<DnsRecord>;
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!(

View File

@ -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);
}
}

View File

@ -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<RwLock<opnsense_config::Config>>,
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!(
"<staticmap>
<mac>{mac}</mac>
<ipaddr>{ip}</ipaddr>
<hostname>{name}</hostname>
<descr>{name}</descr>
<winsserver/>
<dnsserver/>
<ntpserver/>
</staticmap>"
);
// XML Path : <opnsense><dhcpd><DHCPD_INTERFACE_NAME><staticmap>
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,

View File

@ -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(

View File

@ -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"]

View File

@ -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)]

View File

@ -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<String, Error>;
async fn apply_new_config(&self, content: &str) -> Result<(), Error>;
}

View File

@ -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<Config>,
}
@ -78,11 +78,7 @@ impl SshOPNSenseShell {
wait_for_completion(&mut channel).await
}
pub fn new(
host: (Ipv4Addr, u16),
credentials: SshCredentials,
ssh_config: Arc<Config>,
) -> Self {
pub fn new(host: (IpAddr, u16), credentials: SshCredentials, ssh_config: Arc<Config>) -> Self {
Self {
host,
credentials,

View File

@ -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<StaticMap> {