Merge pull request 'feat/opnsenseDNS' (#6) from feat/opnsenseDNS into master
Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/6
This commit is contained in:
commit
58f81f0e58
51
harmony-rs/Cargo.lock
generated
51
harmony-rs/Cargo.lock
generated
@ -1,6 +1,6 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
@ -152,7 +152,7 @@ checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -471,7 +471,7 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -499,7 +499,7 @@ checksum = "2cdc8d50f426189eef89dac62fabfa0abb27d5cc008f25bf4156a0203325becc"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -784,7 +784,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -905,6 +905,15 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "harmony_macros"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"harmony",
|
||||
"quote",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.14.5"
|
||||
@ -1346,7 +1355,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1620,9 +1629,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.86"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
@ -2062,7 +2071,7 @@ checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2247,9 +2256,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.77"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@ -2319,7 +2328,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2371,7 +2380,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2542,7 +2551,7 @@ dependencies = [
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
|
||||
@ -2576,7 +2585,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
"wasm-bindgen-backend",
|
||||
"wasm-bindgen-shared",
|
||||
]
|
||||
@ -2786,6 +2795,16 @@ dependencies = [
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wk"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cidr",
|
||||
"harmony",
|
||||
"harmony_macros",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wyz"
|
||||
version = "0.5.1"
|
||||
@ -2845,7 +2864,7 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.77",
|
||||
"syn 2.0.90",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -3,7 +3,7 @@ resolver = "2"
|
||||
members = [
|
||||
"private_repos/*",
|
||||
"harmony",
|
||||
"opnsense-config", "opnsense-config-xml",
|
||||
"opnsense-config", "opnsense-config-xml", "harmony_macros",
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
|
@ -96,10 +96,10 @@ pub enum StorageKind {
|
||||
}
|
||||
#[derive(Debug, new, Clone)]
|
||||
pub struct Storage {
|
||||
connection: StorageConnectionType,
|
||||
kind: StorageKind,
|
||||
size: u64,
|
||||
serial: String,
|
||||
pub connection: StorageConnectionType,
|
||||
pub kind: StorageKind,
|
||||
pub size: u64,
|
||||
pub serial: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
@ -7,7 +7,6 @@ use super::{
|
||||
data::{Id, Version},
|
||||
executors::ExecutorError,
|
||||
inventory::Inventory,
|
||||
score::Score,
|
||||
topology::HAClusterTopology,
|
||||
};
|
||||
|
||||
@ -41,9 +40,13 @@ pub struct Outcome {
|
||||
status: InterpretStatus,
|
||||
message: String,
|
||||
}
|
||||
impl std::fmt::Display for Outcome {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.write_fmt(format_args!("Outcome {}: {}", self.status, self.message))
|
||||
|
||||
impl Outcome {
|
||||
pub fn noop() -> Self {
|
||||
Self {
|
||||
status: InterpretStatus::NOOP,
|
||||
message: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -54,6 +57,7 @@ pub enum InterpretStatus {
|
||||
RUNNING,
|
||||
QUEUED,
|
||||
BLOCKED,
|
||||
NOOP,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InterpretStatus {
|
||||
@ -64,6 +68,7 @@ impl std::fmt::Display for InterpretStatus {
|
||||
InterpretStatus::RUNNING => "RUNNING",
|
||||
InterpretStatus::QUEUED => "QUEUED",
|
||||
InterpretStatus::BLOCKED => "BLOCKED",
|
||||
InterpretStatus::NOOP => "NO_OP",
|
||||
};
|
||||
f.write_str(msg)
|
||||
}
|
||||
|
@ -34,7 +34,79 @@ pub type IpAddress = IpAddr;
|
||||
/// This abstraction focuses on the logical role and services, independent of the physical hardware.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct LogicalHost {
|
||||
/// The set of services this logical host provides
|
||||
/// The IP address of this logical host.
|
||||
pub ip: IpAddress,
|
||||
/// The name of this logical host.
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl LogicalHost {
|
||||
/// Creates a list of `LogicalHost` instances.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `number_hosts` - The number of logical hosts to create.
|
||||
/// * `start_ip` - The starting IP address. Each subsequent host's IP will be incremented.
|
||||
/// * `hostname_prefix` - The prefix for the host names. Host names will be in the form `prefix<index>`.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A `Vec<LogicalHost>` containing the specified number of logical hosts, each with a unique IP and name.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function will panic if adding `number_hosts` to `start_ip` exceeds the valid range of IP addresses.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use std::str::FromStr;
|
||||
/// use harmony::topology::{IpAddress, LogicalHost};
|
||||
///
|
||||
/// let start_ip = IpAddress::from_str("192.168.0.20").unwrap();
|
||||
/// let hosts = LogicalHost::create_hosts(3, start_ip, "worker");
|
||||
///
|
||||
/// assert_eq!(hosts.len(), 3);
|
||||
/// assert_eq!(hosts[0].ip, IpAddress::from_str("192.168.0.20").unwrap());
|
||||
/// assert_eq!(hosts[0].name, "worker0");
|
||||
/// assert_eq!(hosts[1].ip, IpAddress::from_str("192.168.0.21").unwrap());
|
||||
/// assert_eq!(hosts[1].name, "worker1");
|
||||
/// assert_eq!(hosts[2].ip, IpAddress::from_str("192.168.0.22").unwrap());
|
||||
/// assert_eq!(hosts[2].name, "worker2");
|
||||
/// ```
|
||||
pub fn create_hosts(number_hosts: u32, start_ip: IpAddress, hostname_prefix: &str) -> Vec<LogicalHost> {
|
||||
let mut hosts = Vec::with_capacity(number_hosts.try_into().unwrap());
|
||||
for i in 0..number_hosts {
|
||||
let new_ip = increment_ip(start_ip, i).expect("IP address overflow");
|
||||
let name = format!("{}{}", hostname_prefix, i);
|
||||
hosts.push(LogicalHost { ip: new_ip, name });
|
||||
}
|
||||
hosts
|
||||
}
|
||||
}
|
||||
|
||||
/// Increments an IP address by a given value.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `ip` - The starting IP address.
|
||||
/// * `increment` - The amount to add to the IP address.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// A new `IpAddress` that is the result of incrementing the original by `increment`.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This function panics if the resulting IP address exceeds the valid range.
|
||||
fn increment_ip(ip: IpAddress, increment: u32) -> Option<IpAddress> {
|
||||
match ip {
|
||||
IpAddress::V4(ipv4) => {
|
||||
let new_ip = u32::from(ipv4) + increment;
|
||||
Some(IpAddress::V4(new_ip.into()))
|
||||
}
|
||||
IpAddress::V6(_) => {
|
||||
todo!("Ipv6 not supported yet")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,8 +45,10 @@ pub trait DhcpServer: Send + Sync {
|
||||
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)>;
|
||||
async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError>;
|
||||
fn get_ip(&self) -> IpAddress;
|
||||
fn get_host(&self) -> LogicalHost;
|
||||
async fn commit_config(&self) -> Result<(), ExecutorError>;
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for dyn DhcpServer {
|
||||
|
@ -3,9 +3,7 @@ use derive_new::new;
|
||||
use crate::{hardware::ManagementInterface, topology::MacAddress};
|
||||
|
||||
#[derive(new)]
|
||||
pub struct OPNSenseManagementInterface {
|
||||
mac: MacAddress,
|
||||
}
|
||||
pub struct OPNSenseManagementInterface {}
|
||||
|
||||
impl ManagementInterface for OPNSenseManagementInterface {
|
||||
fn boot_to_pxe(&self) {
|
||||
@ -13,7 +11,7 @@ impl ManagementInterface for OPNSenseManagementInterface {
|
||||
}
|
||||
|
||||
fn get_mac_address(&self) -> MacAddress {
|
||||
self.mac.clone()
|
||||
todo!("OPNSense can have multiple mac addresses using SSH. I'm not sure it even belongs in the ManagementInterface trait")
|
||||
}
|
||||
|
||||
fn get_supported_protocol_names(&self) -> String {
|
||||
|
@ -1,9 +1,10 @@
|
||||
mod management;
|
||||
use std::sync::{Arc, Mutex, RwLock, RwLockWriteGuard};
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use log::debug;
|
||||
pub use management::*;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::{
|
||||
executors::ExecutorError,
|
||||
@ -97,14 +98,24 @@ impl LoadBalancer for OPNSenseFirewall {
|
||||
|
||||
#[async_trait]
|
||||
impl DhcpServer for OPNSenseFirewall {
|
||||
async fn commit_config(&self) -> Result<(), ExecutorError> {
|
||||
self.opnsense_config
|
||||
.read()
|
||||
.await
|
||||
.apply()
|
||||
.await
|
||||
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))
|
||||
}
|
||||
|
||||
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError> {
|
||||
let mac: String = String::from(&entry.mac);
|
||||
|
||||
{
|
||||
let mut writable_opnsense = self.opnsense_config.write().unwrap();
|
||||
let mut writable_opnsense = self.opnsense_config.write().await;
|
||||
writable_opnsense
|
||||
.dhcp()
|
||||
.add_static_mapping(&mac, entry.ip, &entry.name).unwrap();
|
||||
.add_static_mapping(&mac, entry.ip, &entry.name)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
debug!("Registered {:?}", entry);
|
||||
@ -125,9 +136,24 @@ impl DhcpServer for OPNSenseFirewall {
|
||||
fn get_ip(&self) -> IpAddress {
|
||||
OPNSenseFirewall::get_ip(self)
|
||||
}
|
||||
|
||||
fn get_host(&self) -> LogicalHost {
|
||||
self.host.clone()
|
||||
}
|
||||
|
||||
async fn set_next_server(&self, ip: IpAddress) -> Result<(), ExecutorError> {
|
||||
let ipv4 = match ip {
|
||||
std::net::IpAddr::V4(ipv4_addr) => ipv4_addr,
|
||||
std::net::IpAddr::V6(_) => todo!("ipv6 not supported yet"),
|
||||
};
|
||||
{
|
||||
let mut writable_opnsense = self.opnsense_config.write().await;
|
||||
writable_opnsense.dhcp().set_next_server(ipv4);
|
||||
debug!("OPNsense dhcp server set next server {ipv4}");
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl DnsServer for OPNSenseFirewall {
|
||||
|
@ -2,3 +2,8 @@ mod domain;
|
||||
pub use domain::*;
|
||||
pub mod infra;
|
||||
pub mod modules;
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use crate::infra::opnsense::OPNSenseFirewall;
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{net::Ipv4Addr, sync::Arc};
|
||||
use std::sync::Arc;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use derive_new::new;
|
||||
@ -11,50 +11,15 @@ use crate::{
|
||||
},
|
||||
interpret::{Interpret, InterpretError, InterpretName, Outcome},
|
||||
inventory::Inventory,
|
||||
topology::{DHCPStaticEntry, HAClusterTopology, HostBinding},
|
||||
topology::{DHCPStaticEntry, HAClusterTopology, HostBinding, IpAddress},
|
||||
};
|
||||
|
||||
use crate::domain::score::Score;
|
||||
|
||||
/// OPNSenseDhcpScore will set static DHCP entries using index based hostname
|
||||
/// and ip addresses.
|
||||
///
|
||||
/// For example :
|
||||
/// ```rust
|
||||
///
|
||||
/// let node1 = todo!(); // Node pointing to clustermember controlplane0 with ip 10.10.0.20 and host with mac 01
|
||||
/// let node2 = todo!(); // Node pointing to clustermember controlplane1 with ip 10.10.0.21 and host with mac 02
|
||||
/// let node3 = todo!(); // Node pointing to clustermember controlplane2 with ip 10.10.0.22 and host with mac 03
|
||||
///
|
||||
/// let score = OPNSenseDhcpScore {
|
||||
/// nodes: vec![node1, node2, node3],
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// Running such a score would create these static entries :
|
||||
///
|
||||
/// ```rust
|
||||
/// let entries = vec![
|
||||
/// DHCPEntry {
|
||||
/// mac: 01,
|
||||
/// ip: 10.10.0.20,
|
||||
/// hostname: "controlplane0"
|
||||
/// }
|
||||
/// DHCPEntry {
|
||||
/// mac: 02,
|
||||
/// ip: 10.10.0.21,
|
||||
/// hostname: "controlplane0"
|
||||
/// }
|
||||
/// DHCPEntry {
|
||||
/// mac: 03,
|
||||
/// ip: 10.10.0.22,
|
||||
/// hostname: "controlplane2"
|
||||
/// }
|
||||
/// ]
|
||||
/// ```
|
||||
#[derive(Debug, new, Clone)]
|
||||
pub struct DhcpScore {
|
||||
host_binding: Vec<HostBinding>,
|
||||
next_server: Option<IpAddress>,
|
||||
}
|
||||
|
||||
impl Score for DhcpScore {
|
||||
@ -78,7 +43,7 @@ pub struct DhcpInterpret {
|
||||
impl DhcpInterpret {
|
||||
pub fn new(score: DhcpScore) -> Self {
|
||||
let version = Version::from("1.0.0").expect("Version should be valid");
|
||||
let name = "OPNSenseDhcpScore".to_string();
|
||||
let name = "DhcpInterpret".to_string();
|
||||
let id = Id::from_string(format!("{name}_{version}"));
|
||||
|
||||
Self {
|
||||
@ -89,6 +54,67 @@ impl DhcpInterpret {
|
||||
status: InterpretStatus::QUEUED,
|
||||
}
|
||||
}
|
||||
async fn add_static_entries(
|
||||
&self,
|
||||
_inventory: &Inventory,
|
||||
topology: &HAClusterTopology,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
let dhcp_entries: Vec<DHCPStaticEntry> = self
|
||||
.score
|
||||
.host_binding
|
||||
.iter()
|
||||
.map(|binding| {
|
||||
let ip = match binding.logical_host.ip {
|
||||
std::net::IpAddr::V4(ipv4) => ipv4,
|
||||
std::net::IpAddr::V6(_) => {
|
||||
unimplemented!("DHCPStaticEntry only supports ipv4 at the moment")
|
||||
}
|
||||
};
|
||||
|
||||
DHCPStaticEntry {
|
||||
name: binding.logical_host.name.clone(),
|
||||
mac: binding.physical_host.cluster_mac(),
|
||||
ip,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
info!("DHCPStaticEntry : {:?}", dhcp_entries);
|
||||
|
||||
let dhcp_server = Arc::new(topology.dhcp_server.clone());
|
||||
info!("DHCP server : {:?}", dhcp_server);
|
||||
|
||||
let number_new_entries = dhcp_entries.len();
|
||||
|
||||
for entry in dhcp_entries.into_iter() {
|
||||
match dhcp_server.add_static_mapping(&entry).await {
|
||||
Ok(_) => info!("Successfully registered DHCPStaticEntry {}", entry),
|
||||
Err(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Outcome::new(
|
||||
InterpretStatus::SUCCESS,
|
||||
format!("Dhcp Interpret registered {} entries", number_new_entries),
|
||||
))
|
||||
}
|
||||
|
||||
async fn set_next_server(
|
||||
&self,
|
||||
_inventory: &Inventory,
|
||||
topology: &HAClusterTopology,
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
match self.score.next_server {
|
||||
Some(next_server) => {
|
||||
let dhcp_server = Arc::new(topology.dhcp_server.clone());
|
||||
dhcp_server.set_next_server(next_server).await?;
|
||||
Ok(Outcome::new(
|
||||
InterpretStatus::SUCCESS,
|
||||
format!("Dhcp Interpret Set next boot to {next_server}"),
|
||||
))
|
||||
}
|
||||
None => Ok(Outcome::noop()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
@ -116,63 +142,15 @@ impl Interpret for DhcpInterpret {
|
||||
) -> Result<Outcome, InterpretError> {
|
||||
info!("Executing {} on inventory {inventory:?}", self.get_name());
|
||||
|
||||
let dhcp_entries: Vec<DHCPStaticEntry> = self
|
||||
.score
|
||||
.host_binding
|
||||
.iter()
|
||||
.map(|binding| {
|
||||
let ip = match binding.logical_host.ip {
|
||||
std::net::IpAddr::V4(ipv4) => ipv4,
|
||||
std::net::IpAddr::V6(_) => unimplemented!("DHCPStaticEntry only supports ipv4 at the moment"),
|
||||
};
|
||||
self.set_next_server(inventory, topology).await?;
|
||||
|
||||
DHCPStaticEntry {
|
||||
name: binding.logical_host.name.clone(),
|
||||
mac: binding.physical_host.cluster_mac(),
|
||||
ip,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
info!("DHCPStaticEntry : {:?}", dhcp_entries);
|
||||
// self.add_static_entries(inventory, topology).await?;
|
||||
|
||||
let dhcp = Arc::new(Box::new(topology.dhcp_server.clone()));
|
||||
info!("DHCP server : {:?}", dhcp);
|
||||
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");
|
||||
topology.dhcp_server.commit_config().await?;
|
||||
|
||||
Ok(Outcome::new(
|
||||
InterpretStatus::SUCCESS,
|
||||
"Connection test successful".to_string(),
|
||||
format!("Dhcp Interpret execution successful"),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
|
||||
#[test]
|
||||
fn opnsense_dns_score_should_do_nothing_on_empty_inventory() {
|
||||
todo!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opnsense_dns_score_should_set_entry_for_bootstrap_node() {
|
||||
todo!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opnsense_dns_score_should_set_entry_for_control_plane_members() {
|
||||
todo!();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn opnsense_dns_score_should_set_entry_for_workers() {
|
||||
todo!();
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,2 @@
|
||||
pub mod dhcp;
|
||||
pub mod okd;
|
||||
|
45
harmony-rs/harmony/src/modules/okd/dhcp.rs
Normal file
45
harmony-rs/harmony/src/modules/okd/dhcp.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::{
|
||||
inventory::Inventory,
|
||||
modules::dhcp::DhcpScore,
|
||||
score::Score,
|
||||
topology::{HAClusterTopology, HostBinding},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct OKDBootstrapDhcpScore {
|
||||
dhcp_score: DhcpScore,
|
||||
}
|
||||
|
||||
impl OKDBootstrapDhcpScore {
|
||||
pub fn new(topology: &HAClusterTopology, inventory: &Inventory) -> Self {
|
||||
let host_binding = topology
|
||||
.control_plane
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, topology_entry)| HostBinding {
|
||||
logical_host: topology_entry.clone(),
|
||||
physical_host: inventory
|
||||
.control_plane_host
|
||||
.get(index)
|
||||
.expect("Iventory should contain at least as many physical hosts as topology")
|
||||
.clone(),
|
||||
})
|
||||
.collect();
|
||||
Self {
|
||||
dhcp_score: DhcpScore::new(
|
||||
host_binding,
|
||||
// TODO : we should add a tftp server to the topology instead of relying on the
|
||||
// router address, this is leaking implementation details
|
||||
Some(topology.router.get_gateway()),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Score for OKDBootstrapDhcpScore {
|
||||
type InterpretType = <DhcpScore as Score>::InterpretType;
|
||||
|
||||
fn create_interpret(self) -> Self::InterpretType {
|
||||
self.dhcp_score.create_interpret()
|
||||
}
|
||||
}
|
2
harmony-rs/harmony/src/modules/okd/mod.rs
Normal file
2
harmony-rs/harmony/src/modules/okd/mod.rs
Normal file
@ -0,0 +1,2 @@
|
||||
pub mod dhcp;
|
||||
|
46
harmony-rs/harmony_macros/Cargo.lock
generated
Normal file
46
harmony-rs/harmony_macros/Cargo.lock
generated
Normal file
@ -0,0 +1,46 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "harmony_macros"
|
||||
version = "1.0.0"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.92"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.37"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.90"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
|
12
harmony-rs/harmony_macros/Cargo.toml
Normal file
12
harmony-rs/harmony_macros/Cargo.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[package]
|
||||
name = "harmony_macros"
|
||||
edition = "2024"
|
||||
version = "1.0.0"
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
harmony = { version = "0.1.0", path = "../harmony" }
|
||||
quote = "1.0.37"
|
||||
syn = "2.0.90"
|
66
harmony-rs/harmony_macros/src/lib.rs
Normal file
66
harmony-rs/harmony_macros/src/lib.rs
Normal file
@ -0,0 +1,66 @@
|
||||
extern crate proc_macro;
|
||||
|
||||
use harmony::topology::MacAddress;
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
use syn::{LitStr, parse_macro_input};
|
||||
|
||||
#[proc_macro]
|
||||
pub fn ip(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as LitStr);
|
||||
let ip_str = input.value();
|
||||
|
||||
if let Ok(_) = ip_str.parse::<std::net::Ipv4Addr>() {
|
||||
let expanded =
|
||||
quote! { std::net::IpAddr::V4(#ip_str.parse::<std::net::Ipv4Addr>().unwrap()) };
|
||||
return TokenStream::from(expanded);
|
||||
}
|
||||
|
||||
if let Ok(_) = ip_str.parse::<std::net::Ipv6Addr>() {
|
||||
let expanded =
|
||||
quote! { std::net::IpAddr::V4(#ip_str.parse::<std::net::Ipv6Addr>().unwrap()) };
|
||||
return TokenStream::from(expanded);
|
||||
}
|
||||
|
||||
panic!("Invalid IP address: {}", ip_str);
|
||||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn mac_address(input: TokenStream) -> TokenStream {
|
||||
let input = parse_macro_input!(input as LitStr);
|
||||
let mac_str = input.value();
|
||||
|
||||
match parse_mac_address(&mac_str) {
|
||||
Ok(bytes) => {
|
||||
let b0 = bytes[0];
|
||||
let b1 = bytes[1];
|
||||
let b2 = bytes[2];
|
||||
let b3 = bytes[3];
|
||||
let b4 = bytes[4];
|
||||
let b5 = bytes[5];
|
||||
|
||||
quote! {
|
||||
MacAddress( [#b0, #b1, #b2, #b3, #b4, #b5] )
|
||||
}
|
||||
.into()
|
||||
}
|
||||
Err(err) => syn::Error::new(input.span(), err).to_compile_error().into(),
|
||||
}
|
||||
}
|
||||
|
||||
fn parse_mac_address(mac: &str) -> Result<[u8; 6], String> {
|
||||
let parts: Vec<&str> = mac.split(':').collect();
|
||||
if parts.len() != 6 {
|
||||
return Err("MAC address must contain exactly six octets separated by colons".to_string());
|
||||
}
|
||||
|
||||
let mut bytes = [0u8; 6];
|
||||
for (i, part) in parts.iter().enumerate() {
|
||||
match u8::from_str_radix(part, 16) {
|
||||
Ok(byte) => bytes[i] = byte,
|
||||
Err(_) => return Err(format!("Invalid MAC address octet: {}", part)),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(bytes)
|
||||
}
|
@ -16,6 +16,8 @@ pub struct DhcpInterface {
|
||||
pub enable: Option<MaybeString>,
|
||||
pub gateway: Option<MaybeString>,
|
||||
pub domain: Option<MaybeString>,
|
||||
pub netboot: Option<u32>,
|
||||
pub nextserver: Option<String>,
|
||||
#[yaserde(rename = "ddnsdomainalgorithm")]
|
||||
pub ddns_domain_algorithm: Option<MaybeString>,
|
||||
#[yaserde(rename = "numberoptions")]
|
||||
|
@ -27,7 +27,7 @@ pub struct OPNsense {
|
||||
pub opnsense: OPNsenseXmlSection,
|
||||
pub staticroutes: StaticRoutes,
|
||||
pub ca: MaybeString,
|
||||
pub gateways: Option<Gateways>,
|
||||
pub gateways: Option<RawXml>,
|
||||
pub cert: Vec<Cert>,
|
||||
pub dhcpdv6: DhcpDv6,
|
||||
pub virtualip: VirtualIp,
|
||||
@ -60,7 +60,6 @@ impl OPNsense {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct LoadBalancer {
|
||||
pub monitor_type: Vec<MonitorType>,
|
||||
@ -415,6 +414,8 @@ pub struct OPNsenseXmlSection {
|
||||
pub ipsec: Option<IPsec>,
|
||||
#[yaserde(rename = "Interfaces")]
|
||||
pub interfaces: Option<ConfigInterfaces>,
|
||||
#[yaserde(rename = "NodeExporter")]
|
||||
pub node_exporter: Option<RawXml>,
|
||||
#[yaserde(rename = "Kea")]
|
||||
pub kea: Option<RawXml>,
|
||||
pub monit: Option<Monit>,
|
||||
@ -428,6 +429,7 @@ pub struct OPNsenseXmlSection {
|
||||
pub unboundplus: Option<RawXml>,
|
||||
#[yaserde(rename = "DHCRelay")]
|
||||
pub dhcrelay: Option<RawXml>,
|
||||
pub trust: Option<RawXml>,
|
||||
pub wireguard: Option<Wireguard>,
|
||||
#[yaserde(rename = "Swanctl")]
|
||||
pub swanctl: Swanctl,
|
||||
@ -479,6 +481,8 @@ pub struct IDSGeneral {
|
||||
#[yaserde(rename = "LogPayload")]
|
||||
log_payload: Option<u8>,
|
||||
verbosity: MaybeString,
|
||||
#[yaserde(rename = "eveLog")]
|
||||
eve_log: Option<RawXml>,
|
||||
}
|
||||
|
||||
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
|
||||
@ -498,11 +502,15 @@ pub struct IPsec {
|
||||
key_pairs: MaybeString,
|
||||
#[yaserde(rename = "preSharedKeys")]
|
||||
pre_shared_keys: MaybeString,
|
||||
charon: Option<RawXml>,
|
||||
}
|
||||
|
||||
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
|
||||
pub struct GeneralIpsec {
|
||||
enabled: MaybeString,
|
||||
preferred_oldsa: MaybeString,
|
||||
disablevpnrules: MaybeString,
|
||||
passthrough_networks: MaybeString,
|
||||
}
|
||||
|
||||
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
|
||||
@ -1214,6 +1222,8 @@ pub struct WireguardServerItem {
|
||||
pub gateway: MaybeString,
|
||||
pub carp_depend_on: MaybeString,
|
||||
pub peers: String,
|
||||
pub endpoint: MaybeString,
|
||||
pub peer_dns: MaybeString,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
@ -1320,7 +1330,6 @@ pub struct ConfigOpenVPN {
|
||||
pub StaticKeys: MaybeString,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
#[yaserde(rename = "HAProxy")]
|
||||
pub struct HAProxy {
|
||||
@ -1411,6 +1420,8 @@ pub struct Tuning {
|
||||
#[yaserde(rename = "maxConnections")]
|
||||
pub max_connections: MaybeString,
|
||||
pub nbthread: i32,
|
||||
#[yaserde(rename = "resolversPrefer")]
|
||||
pub resolvers_prefer: String,
|
||||
#[yaserde(rename = "sslServerVerify")]
|
||||
pub ssl_server_verify: String,
|
||||
#[yaserde(rename = "maxDHSize")]
|
||||
@ -1425,6 +1436,12 @@ pub struct Tuning {
|
||||
pub lua_max_mem: i32,
|
||||
#[yaserde(rename = "customOptions")]
|
||||
pub custom_options: MaybeString,
|
||||
#[yaserde(rename = "ocspUpdateEnabled")]
|
||||
pub ocs_update_enabled: MaybeString,
|
||||
#[yaserde(rename = "ocspUpdateMinDelay")]
|
||||
pub ocs_update_min_delay: MaybeString,
|
||||
#[yaserde(rename = "ocspUpdateMaxDelay")]
|
||||
pub ocs_update_max_delay: MaybeString,
|
||||
#[yaserde(rename = "ssl_defaultsEnabled")]
|
||||
pub ssl_defaults_enabled: i32,
|
||||
#[yaserde(rename = "ssl_bindOptions")]
|
||||
@ -1437,6 +1454,19 @@ pub struct Tuning {
|
||||
pub ssl_cipher_list: String,
|
||||
#[yaserde(rename = "ssl_cipherSuites")]
|
||||
pub ssl_cipher_suites: String,
|
||||
#[yaserde(rename = "h2_initialWindowSize")]
|
||||
pub h2_initial_window_size: Option<MaybeString>,
|
||||
#[yaserde(rename = "h2_initialWindowSizeOutgoing")]
|
||||
pub h2_initial_window_size_outgoing: Option<MaybeString>,
|
||||
#[yaserde(rename = "h2_initialWindowSizeIncoming")]
|
||||
pub h2_initial_window_size_incoming: Option<MaybeString>,
|
||||
#[yaserde(rename = "h2_maxConcurrentStreams")]
|
||||
pub h2_max_concurrent_streams: Option<MaybeString>,
|
||||
#[yaserde(rename = "h2_maxConcurrentStreamsOutgoing")]
|
||||
pub h2_max_concurrent_streams_outgoing: Option<MaybeString>,
|
||||
#[yaserde(rename = "h2_maxConcurrentStreamsIncoming")]
|
||||
pub h2_max_concurrent_streams_incoming: Option<MaybeString>,
|
||||
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
@ -1684,25 +1714,22 @@ pub struct Backend {
|
||||
pub http2_enabled_nontls: u8,
|
||||
#[yaserde(rename = "ba_advertised_protocols")]
|
||||
pub ba_advertised_protocols: String,
|
||||
#[yaserde(rename = "persistence")]
|
||||
pub persistence: String,
|
||||
#[yaserde(rename = "persistence_cookiemode")]
|
||||
#[yaserde(rename = "forwardFor")]
|
||||
pub forward_for: Option<i32>,
|
||||
#[yaserde(rename = "forwardedHeader")]
|
||||
pub forwarded_header: Option<MaybeString>,
|
||||
#[yaserde(rename = "forwardedHeaderParameters")]
|
||||
pub forwarded_header_parameters: Option<MaybeString>,
|
||||
pub persistence: MaybeString,
|
||||
pub persistence_cookiemode: String,
|
||||
#[yaserde(rename = "persistence_cookiename")]
|
||||
pub persistence_cookiename: MaybeString,
|
||||
#[yaserde(rename = "persistence_stripquotes")]
|
||||
pub persistence_stripquotes: u8,
|
||||
#[yaserde(rename = "stickiness_pattern")]
|
||||
pub stickiness_pattern: String,
|
||||
pub stickiness_pattern: MaybeString,
|
||||
#[yaserde(rename = "stickiness_dataTypes")]
|
||||
pub stickiness_data_types: MaybeString,
|
||||
#[yaserde(rename = "stickiness_expire")]
|
||||
pub stickiness_expire: String,
|
||||
#[yaserde(rename = "stickiness_size")]
|
||||
pub stickiness_size: String,
|
||||
#[yaserde(rename = "stickiness_cookiename")]
|
||||
pub stickiness_cookiename: MaybeString,
|
||||
#[yaserde(rename = "stickiness_cookielength")]
|
||||
pub stickiness_cookielength: MaybeString,
|
||||
#[yaserde(rename = "stickiness_connRatePeriod")]
|
||||
pub stickiness_conn_rate_period: String,
|
||||
@ -1863,12 +1890,6 @@ pub struct StaticRoutes {
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct Ca {}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct Gateways {
|
||||
#[yaserde(rename = "gateway_item")]
|
||||
pub gateway_item: RawXml
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct Cert {
|
||||
#[yaserde(attribute)]
|
||||
@ -1975,14 +1996,14 @@ pub struct Bridges {
|
||||
pub struct Gifs {
|
||||
#[yaserde(attribute)]
|
||||
pub version: Option<String>,
|
||||
pub gif: MaybeString,
|
||||
pub gif: Option<MaybeString>,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
pub struct Gres {
|
||||
#[yaserde(attribute)]
|
||||
pub version: Option<String>,
|
||||
pub gre: MaybeString,
|
||||
pub gre: Option<MaybeString>,
|
||||
}
|
||||
|
||||
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
|
||||
|
@ -24,9 +24,10 @@ impl SshConfigManager {
|
||||
|
||||
impl SshConfigManager {
|
||||
async fn backup_config_remote(&self) -> Result<String, Error> {
|
||||
let backup_filename = format!("config_{}.xml", chrono::Local::now().format("%Y%m%d%H%M%S"));
|
||||
let ts = chrono::Utc::now();
|
||||
let backup_filename = format!("config-{}-harmony.xml", ts.format("%s%.3f"));
|
||||
|
||||
self.opnsense_shell.exec(&format!("cp /conf/config.xml /tmp/{}", backup_filename))
|
||||
self.opnsense_shell.exec(&format!("cp /conf/config.xml /conf/backup/{}", backup_filename))
|
||||
.await
|
||||
}
|
||||
|
||||
|
@ -54,7 +54,9 @@ impl<'a> DhcpConfig<'a> {
|
||||
|
||||
pub fn remove_static_mapping(&mut self, mac: &str) {
|
||||
let lan_dhcpd = self.get_lan_dhcpd();
|
||||
lan_dhcpd.staticmaps.retain(|static_entry| static_entry.mac != mac);
|
||||
lan_dhcpd
|
||||
.staticmaps
|
||||
.retain(|static_entry| static_entry.mac != mac);
|
||||
}
|
||||
|
||||
fn get_lan_dhcpd(&mut self) -> &mut opnsense_config_xml::DhcpInterface {
|
||||
@ -77,16 +79,19 @@ impl<'a> DhcpConfig<'a> {
|
||||
let mac = mac.to_string();
|
||||
let hostname = hostname.to_string();
|
||||
let lan_dhcpd = self.get_lan_dhcpd();
|
||||
let range = &lan_dhcpd.range;
|
||||
let existing_mappings: &mut Vec<StaticMap> = &mut lan_dhcpd.staticmaps;
|
||||
|
||||
if !Self::is_valid_mac(&mac) {
|
||||
return Err(DhcpError::InvalidMacAddress(mac));
|
||||
}
|
||||
|
||||
if !Self::is_ip_in_range(&ipaddr, range) {
|
||||
return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string()));
|
||||
}
|
||||
// TODO verify if address is in subnet range
|
||||
// This check here does not do what we want to do, as we want to assign static leases
|
||||
// outside of the dynamic DHCP pool
|
||||
// let range = &lan_dhcpd.range;
|
||||
// if !Self::is_ip_in_range(&ipaddr, range) {
|
||||
// return Err(DhcpError::IpAddressOutOfRange(ipaddr.to_string()));
|
||||
// }
|
||||
|
||||
if existing_mappings.iter().any(|m| {
|
||||
m.ipaddr
|
||||
@ -123,7 +128,7 @@ impl<'a> DhcpConfig<'a> {
|
||||
|
||||
parts
|
||||
.iter()
|
||||
.all(|part| part.len() == 2 && part.chars().all(|c| c.is_ascii_hexdigit()))
|
||||
.all(|part| part.len() <= 2 && part.chars().all(|c| c.is_ascii_hexdigit()))
|
||||
}
|
||||
|
||||
fn is_ip_in_range(ip: &Ipv4Addr, range: &Range) -> bool {
|
||||
@ -173,6 +178,14 @@ impl<'a> DhcpConfig<'a> {
|
||||
|
||||
Ok(static_maps)
|
||||
}
|
||||
pub fn enable_netboot(&mut self) {
|
||||
self.get_lan_dhcpd().netboot = Some(1);
|
||||
}
|
||||
|
||||
pub fn set_next_server(&mut self, ip: Ipv4Addr) {
|
||||
self.enable_netboot();
|
||||
self.get_lan_dhcpd().nextserver = Some(ip.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
Loading…
Reference in New Issue
Block a user