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

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(