diff --git a/client/src/definitions.ts b/client/src/definitions.ts index 2f4cc5e..f3a7ba2 100644 --- a/client/src/definitions.ts +++ b/client/src/definitions.ts @@ -319,6 +319,7 @@ export const editTypes: { [key: string]: { [key: string]: any } } = { name: 'DNS Server', idType: 'Number', fields: { + name: { is: 'TextBox', label: 'Name'}, interface: { is: 'SingleSelect', label: 'Interface', props: { searchProvider: GetInterfaces} }, comment: { is: 'MultilineTextBox', label: 'Comment'}, }, diff --git a/src/apply/mod.rs b/src/apply/mod.rs index cb332c6..b048064 100644 --- a/src/apply/mod.rs +++ b/src/apply/mod.rs @@ -2,6 +2,7 @@ use thiserror::Error; pub mod chrony; pub mod networkd; +pub mod unbound; #[derive(Error, Debug)] pub enum ApplyError { diff --git a/src/apply/unbound.rs b/src/apply/unbound.rs new file mode 100644 index 0000000..59d3981 --- /dev/null +++ b/src/apply/unbound.rs @@ -0,0 +1,77 @@ +use super::ApplyError; +use crate::{ + definitions::{ + config::Config, + network::{AddressingMode, NetworkInterfaceType}, + }, + templates, +}; +use ipnet::IpNet; +use std::process::Command; +use std::str::FromStr; +use std::{error::Error, io::Write}; +use tera::Context; +use tracing::{error, info}; + +const UNBOUND_CONFIG_PATH: &str = "/etc/unbound/unbound.conf"; +const UNBOUND_TEMPLATE_PATH: &str = "unbound/unbound.conf"; + +pub fn apply_unbound(pending_config: Config, _current_config: Config) -> Result<(), ApplyError> { + let config_data; + let mut context = Context::new(); + let mut interfaces = vec![]; + let mut subnets = vec![]; + + for server in &pending_config.service.dns_servers { + let interface = server.interface(pending_config.clone()); + if let NetworkInterfaceType::Hardware { device } = interface.interface_type.clone() { + interfaces.push(device); + } else { + interfaces.push(interface.name.clone()); + } + + if let AddressingMode::Static { address } = + &server.interface(pending_config.clone()).addressing_mode + { + subnets.push(IpNet::from_str(address)?.network().to_string()); + } + } + context.insert("interfaces", &interfaces); + context.insert("subnets", &subnets); + + match templates::TEMPLATES.render(UNBOUND_TEMPLATE_PATH, &context) { + Ok(s) => config_data = s, + Err(e) => { + error!("Error: {}", e); + let mut cause = e.source(); + while let Some(e) = cause { + error!("Reason: {}", e); + cause = e.source(); + } + return Err(ApplyError::TemplateError(e)); + } + } + + info!("Deleting old Unbound Config"); + std::fs::remove_file(UNBOUND_CONFIG_PATH)?; + + info!("Writing new Unbound Config"); + let mut f = std::fs::File::create(UNBOUND_CONFIG_PATH)?; + f.write_all(config_data.as_bytes())?; + + info!("Restarting Unbound"); + match Command::new("systemctl") + .arg("restart") + .arg("unbound") + .output() + { + Ok(out) => { + if out.status.success() { + Ok(()) + } else { + Err(ApplyError::ServiceRestartFailed) + } + } + Err(err) => Err(ApplyError::IOError(err)), + } +} diff --git a/src/config_manager.rs b/src/config_manager.rs index bb8109c..d58f7b0 100644 --- a/src/config_manager.rs +++ b/src/config_manager.rs @@ -38,6 +38,7 @@ static APPLY_FUNCTIONS: &'static [fn( ) -> Result<(), super::apply::ApplyError>] = &[ super::apply::networkd::apply_networkd, super::apply::chrony::apply_chrony, + super::apply::unbound::apply_unbound, ]; #[derive(Clone)] diff --git a/src/templates/unbound/unbound.conf b/src/templates/unbound/unbound.conf new file mode 100644 index 0000000..960644f --- /dev/null +++ b/src/templates/unbound/unbound.conf @@ -0,0 +1,15 @@ +server: + +# Listen Interfaces +{% for interface in interfaces -%} + interface: {{ interface }} +{% endfor -%} + +# Allowed Networks +{% for subnet in subnets -%} + access-control: {{ subnet }} allow +{% endfor -%} + + +remote-control: + control-enable: yes \ No newline at end of file