forked from NationTech/harmony
		
	Merge pull request 'feat: Add score and opnsense implementation to register dhcp leases in dns server' (#8) from feat/dnsRegisterDhcpLeases into master
Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/8
This commit is contained in:
		
						commit
						f7e97f5c81
					
				
							
								
								
									
										11
									
								
								harmony-rs/Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										11
									
								
								harmony-rs/Cargo.lock
									
									
									
										generated
									
									
									
								
							| @ -711,6 +711,17 @@ dependencies = [ | ||||
|  "percent-encoding", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "fqm" | ||||
| version = "0.1.0" | ||||
| dependencies = [ | ||||
|  "cidr", | ||||
|  "env_logger", | ||||
|  "harmony", | ||||
|  "log", | ||||
|  "tokio", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "funty" | ||||
| version = "2.0.0" | ||||
|  | ||||
| @ -12,12 +12,14 @@ use super::{ | ||||
| 
 | ||||
| pub enum InterpretName { | ||||
|     OPNSenseDHCP, | ||||
|     OPNSenseDns | ||||
| } | ||||
| 
 | ||||
| impl std::fmt::Display for InterpretName { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         match self { | ||||
|             InterpretName::OPNSenseDHCP => f.write_str("OPNSenseDHCP"), | ||||
|             InterpretName::OPNSenseDns => f.write_str("OPNSenseDns"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -57,7 +57,9 @@ impl std::fmt::Debug for dyn DhcpServer { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| pub trait DnsServer: Send + Sync { | ||||
|     async fn register_dhcp_leases(&self, register: bool) -> Result<(), ExecutorError>; | ||||
|     fn add_record( | ||||
|         &mut self, | ||||
|         name: &str, | ||||
| @ -72,6 +74,7 @@ pub trait DnsServer: Send + Sync { | ||||
|     fn list_records(&self) -> Vec<DnsRecord>; | ||||
|     fn get_ip(&self) -> IpAddress; | ||||
|     fn get_host(&self) -> LogicalHost; | ||||
|     async fn commit_config(&self) -> Result<(), ExecutorError>; | ||||
| } | ||||
| 
 | ||||
| impl std::fmt::Debug for dyn DnsServer { | ||||
|  | ||||
| @ -156,6 +156,7 @@ impl DhcpServer for OPNSenseFirewall { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl DnsServer for OPNSenseFirewall { | ||||
|     fn add_record( | ||||
|         &mut self, | ||||
| @ -185,4 +186,26 @@ impl DnsServer for OPNSenseFirewall { | ||||
|     fn get_host(&self) -> LogicalHost { | ||||
|         self.host.clone() | ||||
|     } | ||||
| 
 | ||||
|     async fn register_dhcp_leases(&self, register: bool) -> Result<(), ExecutorError> { | ||||
|         let mut writable_opnsense = self.opnsense_config.write().await; | ||||
|         let mut dns = writable_opnsense.dns(); | ||||
|         dns.register_dhcp_leases(register); | ||||
| 
 | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn commit_config(&self) -> Result<(), ExecutorError> { | ||||
|         let opnsense = self.opnsense_config.read().await; | ||||
| 
 | ||||
|         opnsense | ||||
|             .save() | ||||
|             .await | ||||
|             .map_err(|e| ExecutorError::UnexpectedError(e.to_string()))?; | ||||
| 
 | ||||
|         opnsense | ||||
|             .restart_dns() | ||||
|             .await | ||||
|             .map_err(|e| ExecutorError::UnexpectedError(e.to_string())) | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										101
									
								
								harmony-rs/harmony/src/modules/dns.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								harmony-rs/harmony/src/modules/dns.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,101 @@ | ||||
| use async_trait::async_trait; | ||||
| use derive_new::new; | ||||
| use log::info; | ||||
| 
 | ||||
| use crate::{ | ||||
|     data::{Id, Version}, | ||||
|     interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, | ||||
|     inventory::Inventory, | ||||
|     score::Score, | ||||
|     topology::HAClusterTopology, | ||||
| }; | ||||
| 
 | ||||
| #[derive(Debug, new, Clone)] | ||||
| pub struct DnsScore { | ||||
|     register_dhcp_leases: Option<bool>, | ||||
| } | ||||
| 
 | ||||
| impl Score for DnsScore { | ||||
|     type InterpretType = DnsInterpret; | ||||
| 
 | ||||
|     fn create_interpret(self) -> Self::InterpretType { | ||||
|         DnsInterpret::new(self) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // https://docs.opnsense.org/manual/dhcp.html#advanced-settings
 | ||||
| #[derive(Debug, Clone)] | ||||
| pub struct DnsInterpret { | ||||
|     score: DnsScore, | ||||
|     version: Version, | ||||
|     id: Id, | ||||
|     name: String, | ||||
|     status: InterpretStatus, | ||||
| } | ||||
| 
 | ||||
| impl DnsInterpret { | ||||
|     pub fn new(score: DnsScore) -> Self { | ||||
|         let version = Version::from("1.0.0").expect("Version should be valid"); | ||||
|         let name = "DnsInterpret".to_string(); | ||||
|         let id = Id::from_string(format!("{name}_{version}")); | ||||
| 
 | ||||
|         Self { | ||||
|             version, | ||||
|             id, | ||||
|             name, | ||||
|             score, | ||||
|             status: InterpretStatus::QUEUED, | ||||
|         } | ||||
|     } | ||||
|     async fn serve_dhcp_entries( | ||||
|         &self, | ||||
|         _inventory: &Inventory, | ||||
|         topology: &HAClusterTopology, | ||||
|     ) -> Result<Outcome, InterpretError> { | ||||
|         let dns = topology.dns_server.clone(); | ||||
|         if let Some(register) = self.score.register_dhcp_leases { | ||||
|             dns.register_dhcp_leases(register).await?; | ||||
|         } | ||||
| 
 | ||||
|         Ok(Outcome::new( | ||||
|             InterpretStatus::SUCCESS, | ||||
|             "DNS Interpret execution successfull".to_string(), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[async_trait] | ||||
| impl Interpret for DnsInterpret { | ||||
|     fn get_name(&self) -> InterpretName { | ||||
|         InterpretName::OPNSenseDns | ||||
|     } | ||||
| 
 | ||||
|     fn get_version(&self) -> crate::domain::data::Version { | ||||
|         self.version.clone() | ||||
|     } | ||||
| 
 | ||||
|     fn get_status(&self) -> InterpretStatus { | ||||
|         self.status.clone() | ||||
|     } | ||||
| 
 | ||||
|     fn get_children(&self) -> Vec<crate::domain::data::Id> { | ||||
|         todo!() | ||||
|     } | ||||
| 
 | ||||
|     async fn execute( | ||||
|         &self, | ||||
|         inventory: &Inventory, | ||||
|         topology: &HAClusterTopology, | ||||
|     ) -> Result<Outcome, InterpretError> { | ||||
|         info!("Executing {} on inventory {inventory:?}", self.get_name()); | ||||
| 
 | ||||
|         self.serve_dhcp_entries(inventory, topology).await?; | ||||
| 
 | ||||
|         topology.dns_server.commit_config().await?; | ||||
| 
 | ||||
|         Ok(Outcome::new( | ||||
|             InterpretStatus::SUCCESS, | ||||
|             format!("Dns Interpret execution successful"), | ||||
|         )) | ||||
|     } | ||||
| } | ||||
| @ -1,2 +1,3 @@ | ||||
| pub mod dhcp; | ||||
| pub mod dns; | ||||
| pub mod okd; | ||||
|  | ||||
| @ -426,7 +426,7 @@ pub struct OPNsenseXmlSection { | ||||
|     pub syslog: Option<ConfigSyslog>, | ||||
|     #[yaserde(rename = "TrafficShaper")] | ||||
|     pub traffic_shaper: Option<RawXml>, | ||||
|     pub unboundplus: Option<RawXml>, | ||||
|     pub unboundplus: Option<UnboundPlus>, | ||||
|     #[yaserde(rename = "DHCRelay")] | ||||
|     pub dhcrelay: Option<RawXml>, | ||||
|     pub trust: Option<RawXml>, | ||||
| @ -858,17 +858,17 @@ pub struct Proxy { | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| pub struct ConfigGeneral { | ||||
|     pub enabled: i32, | ||||
|     pub enabled: i8, | ||||
|     #[yaserde(rename = "error_pages")] | ||||
|     pub error_pages: String, | ||||
|     pub icpPort: MaybeString, | ||||
|     pub logging: Logging, | ||||
|     pub alternateDNSservers: MaybeString, | ||||
|     pub dnsV4First: i32, | ||||
|     pub dnsV4First: i8, | ||||
|     pub forwardedForHandling: String, | ||||
|     pub uriWhitespaceHandling: String, | ||||
|     pub enablePinger: i32, | ||||
|     pub useViaHeader: i32, | ||||
|     pub enablePinger: i8, | ||||
|     pub useViaHeader: i8, | ||||
|     pub suppressVersion: i32, | ||||
|     pub connecttimeout: MaybeString, | ||||
|     #[yaserde(rename = "VisibleEmail")] | ||||
| @ -889,8 +889,8 @@ pub struct Logging { | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| pub struct Enable { | ||||
|     pub accessLog: i32, | ||||
|     pub storeLog: i32, | ||||
|     pub accessLog: i8, | ||||
|     pub storeLog: i8, | ||||
| } | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| @ -900,7 +900,7 @@ pub struct Cache { | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| pub struct LocalCache { | ||||
|     pub enabled: i32, | ||||
|     pub enabled: i8, | ||||
|     pub directory: String, | ||||
|     pub cache_mem: i32, | ||||
|     pub maximum_object_size: MaybeString, | ||||
| @ -1069,7 +1069,7 @@ pub struct UnboundPlus { | ||||
|     pub dots: MaybeString, | ||||
|     pub hosts: Hosts, | ||||
|     pub aliases: MaybeString, | ||||
|     pub domains: MaybeString, | ||||
|     pub domains: Option<MaybeString>, | ||||
| } | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| @ -1082,9 +1082,9 @@ pub struct UnboundGeneral { | ||||
|     pub dns64: MaybeString, | ||||
|     pub dns64prefix: MaybeString, | ||||
|     pub noarecords: MaybeString, | ||||
|     pub regdhcp: i32, | ||||
|     pub regdhcp: i8, | ||||
|     pub regdhcpdomain: MaybeString, | ||||
|     pub regdhcpstatic: i32, | ||||
|     pub regdhcpstatic: i8, | ||||
|     pub noreglladdr6: MaybeString, | ||||
|     pub noregrecords: MaybeString, | ||||
|     pub txtsupport: MaybeString, | ||||
| @ -1096,12 +1096,13 @@ pub struct UnboundGeneral { | ||||
| 
 | ||||
| #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] | ||||
| pub struct Advanced { | ||||
|     pub hideidentity: i32, | ||||
|     pub hideversion: i32, | ||||
|     pub prefetch: i32, | ||||
|     pub prefetchkey: i32, | ||||
|     pub dnssecstripped: i32, | ||||
|     pub serveexpired: i32, | ||||
|     pub hideidentity: i8, | ||||
|     pub hideversion: i8, | ||||
|     pub prefetch: i8, | ||||
|     pub prefetchkey: i8, | ||||
|     pub dnssecstripped: i8, | ||||
|     pub aggressivensec: i8, | ||||
|     pub serveexpired: i8, | ||||
|     pub serveexpiredreplyttl: MaybeString, | ||||
|     pub serveexpiredttl: MaybeString, | ||||
|     pub serveexpiredttlreset: i32, | ||||
| @ -1125,6 +1126,7 @@ pub struct Advanced { | ||||
|     pub numqueriesperthread: MaybeString, | ||||
|     pub outgoingrange: MaybeString, | ||||
|     pub jostletimeout: MaybeString, | ||||
|     pub discardtimeout: MaybeString, | ||||
|     pub cachemaxttl: MaybeString, | ||||
|     pub cachemaxnegativettl: MaybeString, | ||||
|     pub cacheminttl: MaybeString, | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| use std::{net::Ipv4Addr, sync::Arc, time::Duration}; | ||||
| 
 | ||||
| use crate::{config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, error::Error, modules::dhcp::DhcpConfig}; | ||||
| use crate::{config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, error::Error, modules::{dhcp::DhcpConfig, dns::DnsConfig}}; | ||||
| use log::trace; | ||||
| use opnsense_config_xml::OPNsense; | ||||
| use russh::client; | ||||
| @ -35,6 +35,25 @@ impl Config { | ||||
|         DhcpConfig::new(&mut self.opnsense, self.shell.clone()) | ||||
|     } | ||||
| 
 | ||||
|     pub fn dns(&mut self) -> DnsConfig { | ||||
|         DnsConfig::new(&mut self.opnsense, self.shell.clone()) | ||||
|     } | ||||
| 
 | ||||
|     pub async fn restart_dns(&self) -> Result<(), Error> { | ||||
|         self.shell.exec("configctl unbound restart").await?; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     /// Save the config to the repository. This method is meant NOT to reload services, only save
 | ||||
|     /// the config to the live file/database and perhaps take a backup when relevant.
 | ||||
|     pub async fn save(&self) -> Result<(), Error> { | ||||
|         self.repository | ||||
|             .save_config(&self.opnsense.to_xml()) | ||||
|             .await | ||||
|     } | ||||
| 
 | ||||
|     /// Save the configuration and reload all services. Be careful with this one as it will cause
 | ||||
|     /// downtime in many cases, such as a PPPoE renegociation
 | ||||
|     pub async fn apply(&self) -> Result<(), Error> { | ||||
|         self.repository | ||||
|             .apply_new_config(&self.opnsense.to_xml()) | ||||
|  | ||||
| @ -20,7 +20,11 @@ impl ConfigManager for LocalFileConfigManager { | ||||
|         Ok(fs::read_to_string(&self.file_path)?) | ||||
|     } | ||||
| 
 | ||||
|     async fn apply_new_config(&self, content: &str) -> Result<(), Error> { | ||||
|     async fn save_config(&self, content: &str) -> Result<(), Error> { | ||||
|         Ok(fs::write(&self.file_path, content)?) | ||||
|     } | ||||
| 
 | ||||
|     async fn apply_new_config(&self, content: &str) -> Result<(), Error> { | ||||
|         self.save_config(content).await | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -9,5 +9,6 @@ use crate::Error; | ||||
| #[async_trait] | ||||
| pub trait ConfigManager: std::fmt::Debug + Send + Sync { | ||||
|     async fn load_as_str(&self) -> Result<String, Error>; | ||||
|     async fn save_config(&self, content: &str) -> Result<(), Error>; | ||||
|     async fn apply_new_config(&self, content: &str) -> Result<(), Error>; | ||||
| } | ||||
|  | ||||
| @ -50,13 +50,18 @@ impl ConfigManager for SshConfigManager { | ||||
|         self.opnsense_shell.exec("cat /conf/config.xml").await | ||||
|     } | ||||
| 
 | ||||
|     async fn apply_new_config(&self, content: &str) -> Result<(), Error> { | ||||
|     async fn save_config(&self, content: &str) -> Result<(), Error> { | ||||
|         let temp_filename = self | ||||
|             .opnsense_shell | ||||
|             .write_content_to_temp_file(content) | ||||
|             .await?; | ||||
|         self.backup_config_remote().await?; | ||||
|         self.move_to_live_config(&temp_filename).await?; | ||||
|         Ok(()) | ||||
|     } | ||||
| 
 | ||||
|     async fn apply_new_config(&self, content: &str) -> Result<(), Error> { | ||||
|         self.save_config(content).await?; | ||||
|         self.reload_all_services().await?; | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
							
								
								
									
										29
									
								
								harmony-rs/opnsense-config/src/modules/dns.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								harmony-rs/opnsense-config/src/modules/dns.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| use std::sync::Arc; | ||||
| 
 | ||||
| use opnsense_config_xml::OPNsense; | ||||
| 
 | ||||
| use crate::config::OPNsenseShell; | ||||
| 
 | ||||
| pub struct DnsConfig<'a> { | ||||
|     opnsense: &'a mut OPNsense, | ||||
|     opnsense_shell: Arc<dyn OPNsenseShell>, | ||||
| } | ||||
| 
 | ||||
| impl<'a> DnsConfig<'a> { | ||||
|     pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self { | ||||
|         Self { | ||||
|             opnsense, | ||||
|             opnsense_shell, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     pub fn register_dhcp_leases(&mut self, register: bool) { | ||||
|         let unbound = match &mut self.opnsense.opnsense.unboundplus { | ||||
|             Some(unbound) => unbound, | ||||
|             None => todo!("Handle case where unboundplus is not used"), | ||||
|         }; | ||||
| 
 | ||||
|         unbound.general.regdhcp = register as i8; | ||||
|         unbound.general.regdhcpstatic = register as i8; | ||||
|     } | ||||
| } | ||||
| @ -1 +1,2 @@ | ||||
| pub mod dhcp; | ||||
| pub mod dns; | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user