diff --git a/client/src/definitions.ts b/client/src/definitions.ts index f62861b..f0bdb00 100644 --- a/client/src/definitions.ts +++ b/client/src/definitions.ts @@ -269,6 +269,7 @@ export const editTypes: { [key: string]: { [key: string]: any } } = { name: 'DHCP Server', idType: 'Number', fields: { + name: { is: 'TextBox', label: 'Name'}, interface: { is: 'SingleSelect', label: 'Interface', props: { searchProvider: GetInterfaces} }, pool: { is: 'MultiSelect', label: 'Pool', props: { searchProvider: GetAddresses} }, gateway_mode: { is: 'EnumInput', label: 'Gateway Mode', props: { variants: { @@ -287,7 +288,7 @@ export const editTypes: { [key: string]: { [key: string]: any } } = { 'specify': { display: 'Specify', fields: { - servers: { is: 'MultiSelect', label: 'DNS Servers', props: { searchProvider: GetAddresses} }, + dns_servers: { is: 'MultiSelect', label: 'DNS Servers', props: { searchProvider: GetAddresses} }, }, }, }}}, @@ -297,12 +298,11 @@ export const editTypes: { [key: string]: { [key: string]: any } } = { 'specify': { display: 'Specify', fields: { - servers: { is: 'MultiSelect', label: 'NTP Servers', props: { searchProvider: GetAddresses} }, + ntp_servers: { is: 'MultiSelect', label: 'NTP Servers', props: { searchProvider: GetAddresses} }, }, }, }}}, - default_lease_time: { is: 'NumberBox', label: 'Default Lease Time'}, - max_lease_time: { is: 'NumberBox', label: 'Max Lease Time'}, + lease_time: { is: 'NumberBox', label: 'Lease Time'}, comment: { is: 'MultilineTextBox', label: 'Comment'}, }, }, diff --git a/src/apply/kea.rs b/src/apply/kea.rs new file mode 100644 index 0000000..82a1e7d --- /dev/null +++ b/src/apply/kea.rs @@ -0,0 +1,257 @@ +use super::ApplyError; +use crate::definitions::{ + config::Config, + network::{AddressingMode, NetworkInterfaceType}, + object::AddressType, + service::{DNSServerMode, GatewayMode, NTPServerMode}, +}; +use ipnet::IpNet; +use serde::Serialize; +use std::io::Write; +use std::process::Command; +use tracing::info; + +const KEA_V4_CONFIG_PATH: &str = "/etc/kea/kea-dhcp4.conf"; +// const KEA_V6_CONFIG_PATH: &str = "/etc/kea/kea-dhcp6.conf"; + +#[derive(Serialize, Clone, Debug)] +pub struct KeaConfig { + #[serde(rename = "Dhcp4")] + pub dhcpv4: KeaDHCPv4, +} + +#[derive(Serialize, Clone, Debug)] +pub struct KeaDHCPv4 { + #[serde(rename = "valid-lifetime")] + pub valid_lifetime: u64, + #[serde(rename = "renew-timer")] + pub renew_timer: u64, + #[serde(rename = "rebind-timer")] + pub rebind_timer: u64, + + #[serde(rename = "interfaces-config")] + pub interfaces_config: KeaInterfaces, + + #[serde(rename = "lease-database")] + pub lease_database: KeaLeases, + + #[serde(rename = "subnet4")] + pub subnet4: Vec, +} + +#[derive(Serialize, Clone, Debug)] +pub struct KeaInterfaces { + #[serde(rename = "interfaces")] + pub interfaces: Vec, +} + +#[derive(Serialize, Clone, Debug)] +pub struct KeaLeases { + #[serde(rename = "type")] + pub database_type: String, + #[serde(rename = "persist")] + pub persist: bool, + #[serde(rename = "name")] + pub name: String, +} + +#[derive(Serialize, Clone, Debug)] +pub struct KeaSubnet4 { + // TODO add subnet Id https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp4-srv.html#ipv4-subnet-identifier + #[serde(rename = "subnet")] + pub subnet: IpNet, + #[serde(rename = "pools")] + pub pools: Vec, + #[serde(rename = "valid-lifetime")] + pub valid_lifetime: u64, + #[serde(rename = "option-data")] + pub option_data: Vec, +} + +#[derive(Serialize, Clone, Debug)] +pub struct KeaPool { + #[serde(rename = "pool")] + pub pool: String, +} + +#[derive(Serialize, Clone, Debug)] +pub struct KeaOption { + #[serde(rename = "code")] + pub code: u64, + #[serde(rename = "data")] + pub data: String, +} + +pub fn apply_kea(pending_config: Config, _current_config: Config) -> Result<(), ApplyError> { + let mut conf: KeaConfig = KeaConfig { + dhcpv4: KeaDHCPv4 { + valid_lifetime: 4000, + renew_timer: 1000, + rebind_timer: 2000, + interfaces_config: KeaInterfaces { interfaces: vec![] }, + lease_database: KeaLeases { + database_type: "memfile".to_string(), + persist: true, + name: "/var/lib/kea/dhcp4.leases".to_string(), + }, + subnet4: vec![], + }, + }; + + for dhcp_server in pending_config.service.dhcp_servers.clone() { + let interface = dhcp_server.interface(pending_config.clone()); + // TODO specify main ip of interface https://kea.readthedocs.io/en/kea-2.2.0/arm/dhcp4-srv.html#interface-configuration + match interface.interface_type { + NetworkInterfaceType::Hardware { device } => { + conf.dhcpv4.interfaces_config.interfaces.push(device) + } + _ => conf + .dhcpv4 + .interfaces_config + .interfaces + .push(interface.name), + } + + let mut subnet = KeaSubnet4 { + subnet: match interface.addressing_mode { + AddressingMode::Static { address } => address.clone(), + _ => panic!("Unsupported Addressing mode"), + }, + pools: vec![], + valid_lifetime: dhcp_server.lease_time, + option_data: vec![], + }; + + match dhcp_server.gateway_mode.clone() { + GatewayMode::Interface => subnet.option_data.push(KeaOption { + code: 3, + data: match interface.addressing_mode { + AddressingMode::Static { address } => address.addr().to_string(), + _ => panic!("Unsupported Address Type"), + }, + }), + GatewayMode::Specify { .. } => subnet.option_data.push(KeaOption { + code: 3, + data: match dhcp_server + .gateway_mode + .gateway(pending_config.clone()) + .address_type + { + AddressType::Host { address } => address.to_string(), + _ => panic!("Unsupported Address Type"), + }, + }), + GatewayMode::None => (), + } + + match dhcp_server.dns_server_mode.clone() { + DNSServerMode::Interface => subnet.option_data.push(KeaOption { + code: 6, + data: match interface.addressing_mode { + AddressingMode::Static { address } => address.addr().to_string(), + _ => panic!("Unsupported Address Type"), + }, + }), + DNSServerMode::Specify { .. } => { + let mut servers = "".to_string(); + let dns_servers = dhcp_server + .dns_server_mode + .dns_servers(pending_config.clone()); + + for i in 0..dns_servers.len() { + match dns_servers[i].address_type { + AddressType::Host { address } => { + if i > 0 { + servers += ", "; + } + servers += &address.to_string(); + } + _ => panic!("Unsupported Address Type"), + } + } + + subnet.option_data.push(KeaOption { + code: 6, + data: servers, + }); + } + DNSServerMode::None => (), + } + + match dhcp_server.ntp_server_mode.clone() { + NTPServerMode::Interface => subnet.option_data.push(KeaOption { + code: 42, + data: match interface.addressing_mode { + AddressingMode::Static { address } => address.addr().to_string(), + _ => panic!("Unsupported Address Type"), + }, + }), + NTPServerMode::Specify { .. } => { + let mut servers = "".to_string(); + let ntp_servers = dhcp_server + .ntp_server_mode + .ntp_servers(pending_config.clone()); + + for i in 0..ntp_servers.len() { + match ntp_servers[i].address_type { + AddressType::Host { address } => { + if i > 0 { + servers += ", "; + } + servers += &address.to_string(); + } + _ => panic!("Unsupported Address Type"), + } + } + + subnet.option_data.push(KeaOption { + code: 42, + data: servers, + }); + } + NTPServerMode::None => (), + } + + let pools = dhcp_server.pool(pending_config.clone()); + for pool in pools { + match pool.address_type { + AddressType::Host { address } => subnet.pools.push(KeaPool { + pool: address.to_string() + "/32", + }), + AddressType::Range { .. } => panic!("TODO fix range type"), + AddressType::Network { network } => subnet.pools.push(KeaPool { + pool: network.to_string(), + }), + AddressType::Group { .. } => panic!("TODO"), + } + } + + conf.dhcpv4.subnet4.push(subnet); + } + + info!("Serializeing Kea v4 Config"); + let config_data: String = serde_json::to_string_pretty(&conf)?; + + info!("Deleting old Kea v4 Config"); + std::fs::remove_file(KEA_V4_CONFIG_PATH)?; + + info!("Writing new Kea v4 Config"); + let mut f = std::fs::File::create(KEA_V4_CONFIG_PATH)?; + f.write_all(config_data.as_bytes())?; + + info!("Restarting Kea"); + match Command::new("systemctl") + .arg("restart") + .arg("kea-dhcp4") + .output() + { + Ok(out) => { + if out.status.success() { + Ok(()) + } else { + Err(ApplyError::ServiceRestartFailed) + } + } + Err(err) => Err(ApplyError::IOError(err)), + } +} diff --git a/src/apply/mod.rs b/src/apply/mod.rs index 627467d..93e867b 100644 --- a/src/apply/mod.rs +++ b/src/apply/mod.rs @@ -1,6 +1,7 @@ use thiserror::Error; pub mod chrony; +pub mod kea; pub mod networkd; pub mod nftables; pub mod unbound; @@ -16,6 +17,9 @@ pub enum ApplyError { #[error(transparent)] AddrParseError(#[from] ipnet::AddrParseError), + #[error(transparent)] + SerdeError(#[from] serde_json::Error), + #[error("Service Restart Failed")] ServiceRestartFailed, } diff --git a/src/config_manager.rs b/src/config_manager.rs index f746191..915ecc9 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::nftables::apply_nftables, + super::apply::kea::apply_kea, super::apply::chrony::apply_chrony, super::apply::unbound::apply_unbound, ]; diff --git a/src/definitions/service.rs b/src/definitions/service.rs index e87f2e5..973847c 100644 --- a/src/definitions/service.rs +++ b/src/definitions/service.rs @@ -1,4 +1,3 @@ -use core::time; use macaddr::MacAddr8; use serde::{Deserialize, Serialize}; use validator::Validate; @@ -15,11 +14,11 @@ pub struct DHCPServer { pub name: String, pub interface: String, pub pool: Vec, - pub lease_time: time::Duration, + pub lease_time: u64, pub gateway_mode: GatewayMode, pub dns_server_mode: DNSServerMode, pub ntp_server_mode: NTPServerMode, - pub reservations: Vec, + // pub reservations: Vec, pub comment: String, }