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:
johnride 2024-12-18 17:45:54 +00:00
commit f7e97f5c81
13 changed files with 222 additions and 20 deletions

11
harmony-rs/Cargo.lock generated
View File

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

View File

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

View File

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

View File

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

View 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"),
))
}
}

View File

@ -1,2 +1,3 @@
pub mod dhcp;
pub mod dns;
pub mod okd;

View File

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

View File

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

View File

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

View File

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

View File

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

View 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;
}
}

View File

@ -1 +1,2 @@
pub mod dhcp;
pub mod dns;