wip: OPNSense XML config editor coming along

This commit is contained in:
jeangab 2024-09-30 16:20:11 -04:00
parent 465106438a
commit 407bdbc032
10 changed files with 181 additions and 70 deletions

74
harmony-rs/Cargo.lock generated
View File

@ -58,17 +58,6 @@ dependencies = [
"subtle",
]
[[package]]
name = "affilium"
version = "0.1.0"
dependencies = [
"cidr",
"env_logger",
"harmony",
"log",
"tokio",
]
[[package]]
name = "aho-corasick"
version = "1.1.3"
@ -258,6 +247,15 @@ version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50"
[[package]]
name = "castaway"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5"
dependencies = [
"rustversion",
]
[[package]]
name = "cbc"
version = "0.1.2"
@ -315,6 +313,19 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0"
[[package]]
name = "compact_str"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f86b9c4c00838774a6d902ef931eff7470720c51d90c2e32cfe15dc304737b3f"
dependencies = [
"castaway",
"cfg-if",
"itoa",
"ryu",
"static_assertions",
]
[[package]]
name = "const-oid"
version = "0.9.6"
@ -817,6 +828,7 @@ dependencies = [
"env_logger",
"libredfish",
"log",
"minidom",
"reqwest",
"russh",
"rust-ipmi",
@ -1069,6 +1081,15 @@ version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
[[package]]
name = "minidom"
version = "0.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e394a0e3c7ccc2daea3dffabe82f09857b6b510cb25af87d54bf3e910ac1642d"
dependencies = [
"rxml",
]
[[package]]
name = "miniz_oxide"
version = "0.7.4"
@ -1732,6 +1753,31 @@ dependencies = [
"base64",
]
[[package]]
name = "rustversion"
version = "1.0.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6"
[[package]]
name = "rxml"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65bc94b580d0f5a6b7a2d604e597513d3c673154b52ddeccd1d5c32360d945ee"
dependencies = [
"bytes",
"rxml_validation",
]
[[package]]
name = "rxml_validation"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "826e80413b9a35e9d33217b3dcac04cf95f6559d15944b93887a08be5496c4a4"
dependencies = [
"compact_str",
]
[[package]]
name = "ryu"
version = "1.0.18"
@ -1993,6 +2039,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "static_assertions"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "subtle"
version = "2.6.1"

View File

@ -17,3 +17,4 @@ derive-new = "0.7.0"
async-trait = "0.1.82"
tokio = { version = "1.40.0", features = ["io-std"] }
cidr = "0.2.3"
minidom = "0.16.0"

View File

@ -17,3 +17,4 @@ log = { workspace = true }
env_logger = { workspace = true }
async-trait = { workspace = true }
cidr = { workspace = true }
minidom = { workspace = true }

View File

@ -7,6 +7,7 @@ use crate::topology::MacAddress;
pub type HostGroup = Vec<PhysicalHost>;
pub type SwitchGroup = Vec<Switch>;
pub type FirewallGroup = Vec<PhysicalHost>;
#[derive(Debug, Clone)]
pub struct PhysicalHost {
pub category: HostCategory,

View File

@ -23,6 +23,9 @@ use super::{
pub struct Inventory {
pub location: Location,
pub switch: SwitchGroup,
// Firewall is really just a host but with somewhat specialized hardware
// I'm not entirely sure it belongs to its own category but it helps make things easier and
// clearer for now so let's try it this way.
pub firewall: FirewallGroup,
pub worker_host: HostGroup,
pub storage_host: HostGroup,
@ -34,9 +37,11 @@ impl Inventory {
pub fn empty_inventory() -> Self {
Self {
location: Location::test_building(),
host: HostGroup::new(),
switch: SwitchGroup::new(),
firewall: FirewallGroup::new(),
worker_host: HostGroup::new(),
storage_host: HostGroup::new(),
control_plane_host: HostGroup::new(),
}
}
}

View File

@ -1,10 +1,11 @@
use crate::executors::ExecutorError;
use super::{IpAddress, LogicalHost};
pub trait LoadBalancer: Send + Sync {
fn add_backend(&mut self, backend: Backend) -> Result<(), LoadBalancerError>;
fn remove_backend(&mut self, backend_id: &str) -> Result<(), LoadBalancerError>;
fn add_frontend(&mut self, frontend: Frontend) -> Result<(), LoadBalancerError>;
fn remove_frontend(&mut self, frontend_id: &str) -> Result<(), LoadBalancerError>;
fn add_backend(&mut self, backend: Backend) -> Result<(), ExecutorError>;
fn remove_backend(&mut self, backend_id: &str) -> Result<(), ExecutorError>;
fn add_frontend(&mut self, frontend: Frontend) -> Result<(), ExecutorError>;
fn remove_frontend(&mut self, frontend_id: &str) -> Result<(), ExecutorError>;
fn list_backends(&self) -> Vec<Backend>;
fn list_frontends(&self) -> Vec<Frontend>;
fn get_ip(&self) -> IpAddress;
@ -17,8 +18,6 @@ impl std::fmt::Debug for dyn LoadBalancer {
}
}
pub struct LoadBalancerError;
#[derive(Clone, Debug)]
pub struct Backend {
pub id: String,

View File

@ -1,3 +1,7 @@
use async_trait::async_trait;
use crate::executors::ExecutorError;
use super::{IpAddress, LogicalHost};
#[derive(Debug)]
@ -14,8 +18,8 @@ impl std::fmt::Display for DHCPStaticEntry {
}
pub trait Firewall: Send + Sync {
fn add_rule(&mut self, rule: FirewallRule) -> Result<(), FirewallError>;
fn remove_rule(&mut self, rule_id: &str) -> Result<(), FirewallError>;
fn add_rule(&mut self, rule: FirewallRule) -> Result<(), ExecutorError>;
fn remove_rule(&mut self, rule_id: &str) -> Result<(), ExecutorError>;
fn list_rules(&self) -> Vec<FirewallRule>;
fn get_ip(&self) -> IpAddress;
fn get_host(&self) -> LogicalHost;
@ -31,10 +35,11 @@ pub struct NetworkDomain {
pub name: String,
}
#[async_trait]
pub trait DhcpServer: Send + Sync {
fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), DhcpError>;
fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), DhcpError>;
fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError>;
async fn remove_static_mapping(&self, mac: &MacAddress) -> Result<(), ExecutorError>;
async fn list_static_mappings(&self) -> Vec<(MacAddress, IpAddress)>;
fn get_ip(&self) -> IpAddress;
fn get_host(&self) -> LogicalHost;
}
@ -51,8 +56,8 @@ pub trait DnsServer: Send + Sync {
name: &str,
record_type: DnsRecordType,
value: &str,
) -> Result<(), DnsError>;
fn remove_record(&mut self, name: &str, record_type: DnsRecordType) -> Result<(), DnsError>;
) -> 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;
@ -89,6 +94,9 @@ pub enum Action {
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MacAddress(pub [u8; 6]);
// TODO create a small macro to provide a nice API to initiate a MacAddress
// MacAddress::from!("00:90:7f:df:2c:23"),
impl MacAddress {
pub fn dummy() -> Self {
Self([0, 0, 0, 0, 0, 0])
@ -119,8 +127,3 @@ pub struct DnsRecord {
pub record_type: DnsRecordType,
pub value: String,
}
// Error types
pub struct FirewallError;
pub struct DhcpError;
pub struct DnsError;

View File

@ -0,0 +1,43 @@
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 minidom::Element;
use std::fs;
use std::io::{BufReader, Read};
use std::process::Command;
// #[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: Element = file_str.parse().unwrap();
// Save file with suffix name
fs::write(&output_path, String::from(&root)).expect("Failed to write output file");
}
}

View File

@ -1,15 +1,20 @@
mod management;
mod config;
use async_trait::async_trait;
pub use management::*;
use crate::{executors::ExecutorError, topology::{
Backend, DHCPStaticEntry, DhcpServer, DnsServer, Firewall, FirewallError, FirewallRule,
Frontend, IpAddress, LoadBalancer, LoadBalancerError, LogicalHost,
}};
use crate::{
executors::ExecutorError, infra::opnsense::config::OPNSenseXmlConfigEditor, topology::{
Backend, DHCPStaticEntry, DhcpServer, DnsServer, Firewall, FirewallRule, Frontend,
IpAddress, LoadBalancer, LogicalHost,
}
};
use derive_new::new;
#[derive(new, Clone)]
pub struct OPNSenseFirewall {
host: LogicalHost,
cluster_nic_name: String,
}
impl OPNSenseFirewall {
@ -17,17 +22,14 @@ impl OPNSenseFirewall {
self.host.ip
}
fn save_xml_to_config(&self, xml_entry: &str) -> Result<(), ExecutorError> {
todo!("Save XML Entry in opnsense /conf/config.xml {xml_entry}");
}
}
impl Firewall for OPNSenseFirewall {
fn add_rule(&mut self, _rule: FirewallRule) -> Result<(), FirewallError> {
fn add_rule(&mut self, _rule: FirewallRule) -> Result<(), ExecutorError> {
todo!()
}
fn remove_rule(&mut self, _rule_id: &str) -> Result<(), FirewallError> {
fn remove_rule(&mut self, _rule_id: &str) -> Result<(), ExecutorError> {
todo!()
}
@ -44,19 +46,19 @@ impl Firewall for OPNSenseFirewall {
}
impl LoadBalancer for OPNSenseFirewall {
fn add_backend(&mut self, _backend: Backend) -> Result<(), LoadBalancerError> {
fn add_backend(&mut self, _backend: Backend) -> Result<(), ExecutorError> {
todo!()
}
fn remove_backend(&mut self, _backend_id: &str) -> Result<(), LoadBalancerError> {
fn remove_backend(&mut self, _backend_id: &str) -> Result<(), ExecutorError> {
todo!()
}
fn add_frontend(&mut self, _frontend: Frontend) -> Result<(), LoadBalancerError> {
fn add_frontend(&mut self, _frontend: Frontend) -> Result<(), ExecutorError> {
todo!()
}
fn remove_frontend(&mut self, _frontend_id: &str) -> Result<(), LoadBalancerError> {
fn remove_frontend(&mut self, _frontend_id: &str) -> Result<(), ExecutorError> {
todo!()
}
@ -76,36 +78,40 @@ impl LoadBalancer for OPNSenseFirewall {
}
}
#[async_trait]
impl DhcpServer for OPNSenseFirewall {
fn add_static_mapping(
&self,
entry: &DHCPStaticEntry,
) -> Result<(), crate::topology::DhcpError> {
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError> {
let mac = &entry.mac;
let name = &entry.name;
let ip = &entry.ip;
let xml_entry = format!("<staticmap>
> <mac>{mac}</mac>
> <ipaddr>{ip}</ipaddr>
> <hostname>{name}</hostname>
> <descr>{name}</descr>
> <winsserver/>
> <dnsserver/>
> <ntpserver/>
> </staticmap>");
self.save_xml_to_config(&xml_entry)?;
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)
}
fn remove_static_mapping(
async fn remove_static_mapping(
&self,
_mac: &crate::topology::MacAddress,
) -> Result<(), crate::topology::DhcpError> {
) -> Result<(), ExecutorError> {
todo!()
}
fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> {
async fn list_static_mappings(&self) -> Vec<(crate::topology::MacAddress, IpAddress)> {
todo!()
}
@ -122,7 +128,7 @@ impl DnsServer for OPNSenseFirewall {
_name: &str,
_record_type: crate::topology::DnsRecordType,
_value: &str,
) -> Result<(), crate::topology::DnsError> {
) -> Result<(), ExecutorError> {
todo!()
}
@ -130,7 +136,7 @@ impl DnsServer for OPNSenseFirewall {
&mut self,
_name: &str,
_record_type: crate::topology::DnsRecordType,
) -> Result<(), crate::topology::DnsError> {
) -> Result<(), ExecutorError> {
todo!()
}

View File

@ -1,3 +1,5 @@
use std::sync::Arc;
use async_trait::async_trait;
use derive_new::new;
use log::info;
@ -7,7 +9,6 @@ use crate::{
data::{Id, Version},
interpret::InterpretStatus,
},
infra::executors::russh::RusshClient,
interpret::{Interpret, InterpretError, InterpretName, Outcome},
inventory::Inventory,
topology::{DHCPStaticEntry, HAClusterTopology, HostBinding},
@ -114,9 +115,8 @@ impl Interpret for DhcpInterpret {
topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> {
info!("Executing {} on inventory {inventory:?}", self.get_name());
let ssh_client = RusshClient {};
let entries: Vec<DHCPStaticEntry> = self
let dhcp_entries: Vec<DHCPStaticEntry> = self
.score
.host_binding
.iter()
@ -126,16 +126,16 @@ impl Interpret for DhcpInterpret {
ip: binding.logical_host.ip,
})
.collect();
info!("DHCPStaticEntry : {:?}", entries);
info!("DHCPStaticEntry : {:?}", dhcp_entries);
let dhcp = topology.dhcp_server.clone();
let dhcp = Arc::new(Box::new(topology.dhcp_server.clone()));
info!("DHCP server : {:?}", dhcp);
entries.iter().for_each(|entry| {
match dhcp.add_static_mapping(&entry) {
for entry in dhcp_entries.into_iter() {
match dhcp.add_static_mapping(&entry).await {
Ok(_) => info!("Successfully registered DHCPStaticEntry {}", entry),
Err(_) => todo!(),
}
});
}
todo!("Configure DHCPServer");
Ok(Outcome::new(