feat(load balancer): Can now fully configure an OPNSense HAProxy

instance for and openshift/OKD cluster

Squashed commit of the following:

commit f0d90d9e37da925c6b4441b076e212dd4f340cb7
Author: Jean-Gabriel Gill-Couture <jg@nationtech.io>
Date:   Fri Jan 3 10:19:49 2025 -0500

    chore: Remove opnsense config file committe by mistake

commit 73f017e6abc770003c483ee7e121c1c6e3cafa1a
Author: Jean-Gabriel Gill-Couture <jg@nationtech.io>
Date:   Fri Jan 3 10:17:42 2025 -0500

    feat(config): enhance HAProxy load balancer configuration and organize structs

    Add missing fields to the HAProxy load balancer configuration to make it fully functional. Move most of the HAProxy-related structs to their own file within `opnsense-config-xml` for better organization and maintainability.

commit 8a1b0b77dc6dde32298f69b0ca8a24ea2246de9e
Author: Jean-Gabriel Gill-Couture <jg@nationtech.io>
Date:   Tue Dec 31 12:06:37 2024 -0500

    feat(OPNSense): add support for configuration port and load balancer commit

    Introduce an optional port parameter to OPNSense configuration creation and enhance LoadBalancerInterpret to apply configurations after ensuring service existence. Adjust package installation commands to run non-interactively and log outputs. Add methods to enable the HAProxy component in the load balancer configuration.

commit 5075d1146f12cf7df2ae5d66ecee45f056ab39e8
Author: Jean-Gabriel Gill-Couture <jg@nationtech.io>
Date:   Sun Dec 29 09:29:57 2024 -0500

    docs: add README.md for OPNSense demo in vbox-opnsense

    Add a README file with instructions on how to download, start, and run the OPNSense virtual machine using VirtualBox, including credentials and command line usage details.

commit 5cda52430f1db6279707f8d189b7aac10195e09d
Author: Jean-Gabriel Gill-Couture <jg@nationtech.io>
Date:   Sat Dec 28 23:27:45 2024 -0500

    feat(opnsense-config): add package management and refactor load balancer module

    - Introduced `install_package` and `reload_haproxy` methods in the `Config` struct to manage OPNsense packages and reload HAProxy configuration.
    - Refactored `LoadBalancerConfig` to use a helper method `with_haproxy` for modifying HAProxy configurations, improving code readability and reducing duplication.
    - Added TODO warning in `add_backend` method to ensure new backends refer only to existing entities like servers or health checks.

commit d01186d21c443543e278c9e5190317b9961f8112
Author: Jean-Gabriel Gill-Couture <jg@nationtech.io>
Date:   Sat Dec 28 15:11:10 2024 -0500

    feat: Created demo project for virtualbox opnsense

commit c6c92ab1d457f5b2e38cbcfec0660c6fb550df1e
Author: Jean-Gabriel Gill-Couture <jeangabriel.gc@gmail.com>
Date:   Tue Dec 24 08:08:59 2024 -0500

    fix(xml): Support virtualbox config, pretty much vanilla opnsense

commit 4d5c23a6d07a434baa83d608247d0c7c446c1c08
Author: Sylvain Tremblay <stremblay@nationtech.io>
Date:   Fri Dec 20 16:15:18 2024 -0500

    fix: Support st opnsense config

commit 4546e6b5482061e3d51e07451ba182054d44d888
Author: johnride <jg@nationtech.io>
Date:   Fri Dec 20 21:11:56 2024 +0000

    feat: HAProxy load balancer able to load and create services, ready to be tested, they do not upload the new config yet

commit a899811d9bacea10e799a072965350842f9cbc7c
Author: johnride <jg@nationtech.io>
Date:   Fri Dec 20 18:39:32 2024 +0000

    feat: LoadBalancer building haproxy structures wip

commit 653c323f050ead350fd048fb30c1c3717b1471d1
Author: johnride <jg@nationtech.io>
Date:   Fri Dec 20 05:02:52 2024 +0000

    feat: LoadBalancer progress, now handles loading frontend, backend, servers and healthchecks from haproxy xml

commit 737e738f62d22523d5ededa4b6cc0b2e0ac7a0da
Author: jeangab <jeangabriel.gc@gmail.com>
Date:   Thu Dec 19 18:30:58 2024 -0500

    wip: Haproxy coming along, about 80% done before first test

commit 615ed36d89182e4bb1f2312dc9a8e4a7c31ab416
Author: Jean-Gabriel Gill-Couture <jeangabriel.gc@gmail.com>
Date:   Wed Dec 18 22:20:36 2024 -0500

    wip: LoadBalancer score coming along, first part of the score definition done. Next step is finishing it up and writing the concrete HAProxy implementation
This commit is contained in:
Jean-Gabriel Gill-Couture 2025-01-04 10:12:16 -05:00
parent 0b6c8bfd09
commit 098cb30523
31 changed files with 1729 additions and 676 deletions

25
harmony-rs/Cargo.lock generated
View File

@ -711,17 +711,6 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "fqm"
version = "0.1.0"
dependencies = [
"cidr",
"env_logger",
"harmony",
"log",
"tokio",
]
[[package]]
name = "funty"
version = "2.0.0"
@ -904,6 +893,7 @@ dependencies = [
"serde",
"serde_json",
"tokio",
"uuid",
]
[[package]]
@ -1404,6 +1394,7 @@ dependencies = [
"env_logger",
"log",
"pretty_assertions",
"rand",
"serde",
"thiserror",
"tokio",
@ -2526,6 +2517,18 @@ dependencies = [
"syn 2.0.90",
]
[[package]]
name = "vbox-opnpsense"
version = "0.1.0"
dependencies = [
"cidr",
"env_logger",
"harmony",
"harmony_macros",
"log",
"tokio",
]
[[package]]
name = "vcpkg"
version = "0.2.15"

View File

@ -2,6 +2,7 @@
resolver = "2"
members = [
"private_repos/*",
"demo/*",
"harmony",
"opnsense-config", "opnsense-config-xml", "harmony_macros",
]
@ -20,6 +21,16 @@ tokio = { version = "1.40.0", features = ["io-std"] }
cidr = "0.2.3"
russh = "0.45.0"
russh-keys = "0.45.0"
rand = "0.8.5"
[workspace.dependencies.uuid]
version = "1.11.0"
features = [
"v4", # Lets you generate random UUIDs
"fast-rng", # Use a faster (but still sufficiently random) RNG
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
]
#[workspace.target.x86_64-unknown-linux-gnu]
#rustflags = ["-C", "link-arg=-fuse-ld=mold"]

View File

@ -0,0 +1,14 @@
[package]
name = "vbox-opnpsense"
edition = "2024"
version.workspace = true
readme.workspace = true
license.workspace = true
[dependencies]
harmony = { version = "0.1.0", path = "../../harmony" }
cidr = { workspace = true }
tokio = { workspace = true }
harmony_macros = { version = "1.0.0", path = "../../harmony_macros" }
log = { workspace = true }
env_logger = { workspace = true }

View File

@ -0,0 +1,16 @@
## OPNSense demo
Download the virtualbox snapshot from {{TODO URL}}
Start the virtualbox image
This virtualbox image is configured to use a bridge on the host's physical interface, make sure the bridge is up and the virtual machine can reach internet.
Credentials are opnsense default (root/opnsense)
Run the project with the correct ip address on the command line :
```bash
cd demo/vbox-opnsense/
cargo run 192.168.5.229
```

View File

@ -0,0 +1,91 @@
use std::{
net::{IpAddr, Ipv4Addr},
sync::Arc,
};
use cidr::Ipv4Cidr;
use harmony::{
hardware::{FirewallGroup, HostCategory, Location, PhysicalHost, SwitchGroup},
infra::opnsense::OPNSenseManagementInterface,
inventory::Inventory,
maestro::Maestro,
modules::okd::{dhcp::OKDBootstrapDhcpScore, dns::OKDBootstrapDnsScore},
topology::{LogicalHost, UnmanagedRouter},
};
use harmony_macros::ip;
#[tokio::main]
async fn main() {
env_logger::init();
let firewall = harmony::topology::LogicalHost {
ip: ip!("192.168.5.229"),
name: String::from("opnsense-1"),
};
let firewall = harmony::topology::LogicalHost {
ip: ip!("127.0.0.1"),
name: String::from("opnsense-1"),
};
let opnsense = Arc::new(
harmony::infra::opnsense::OPNSenseFirewall::new(
firewall,
Some(2222),
"lan",
"root",
"opnsense",
)
.await,
);
let lan_subnet = Ipv4Addr::new(10, 100, 8, 0);
let gateway_ipv4 = Ipv4Addr::new(10, 100, 8, 1);
let gateway_ip = IpAddr::V4(gateway_ipv4);
let topology = harmony::topology::HAClusterTopology {
domain_name: "demo.harmony.mcd".to_string(),
router: Arc::new(UnmanagedRouter::new(
gateway_ip,
Ipv4Cidr::new(lan_subnet, 24).unwrap(),
)),
load_balancer: opnsense.clone(),
firewall: opnsense.clone(),
dhcp_server: opnsense.clone(),
dns_server: opnsense.clone(),
control_plane: vec![LogicalHost {
ip: ip!("10.100.8.20"),
name: "cp0".to_string(),
}],
workers: vec![],
switch: vec![],
};
let inventory = Inventory {
location: Location::new(
"232 des Éperviers, Wendake, Qc, G0A 4V0".to_string(),
"wk".to_string(),
),
switch: SwitchGroup::from([]),
firewall: FirewallGroup::from([PhysicalHost {
category: HostCategory::Firewall,
network: vec![],
management: Arc::new(OPNSenseManagementInterface::new()),
storage: vec![],
labels: vec![],
}]),
worker_host: vec![],
storage_host: vec![],
control_plane_host: vec![],
};
// TODO regroup smaller scores in a larger one such as this
// let okd_boostrap_preparation();
// let dhcp_score = OKDBootstrapDhcpScore::new(&topology, &inventory);
// let dns_score = OKDBootstrapDnsScore::new(&topology);
let load_balancer_score =
harmony::modules::okd::load_balancer::OKDLoadBalancerScore::new(&topology);
let maestro = Maestro::new(inventory, topology);
// maestro.interpret(dns_score).await.unwrap();
// maestro.interpret(dhcp_score).await.unwrap();
maestro.interpret(load_balancer_score).await.unwrap();
}

View File

@ -19,3 +19,4 @@ async-trait = { workspace = true }
cidr = { workspace = true }
opnsense-config = { path = "../opnsense-config" }
opnsense-config-xml = { path = "../opnsense-config-xml" }
uuid = { workspace = true }

View File

@ -12,7 +12,8 @@ use super::{
pub enum InterpretName {
OPNSenseDHCP,
OPNSenseDns
OPNSenseDns,
LoadBalancer,
}
impl std::fmt::Display for InterpretName {
@ -20,6 +21,7 @@ impl std::fmt::Display for InterpretName {
match self {
InterpretName::OPNSenseDHCP => f.write_str("OPNSenseDHCP"),
InterpretName::OPNSenseDns => f.write_str("OPNSenseDns"),
InterpretName::LoadBalancer => f.write_str("LoadBalancer"),
}
}
}
@ -39,8 +41,8 @@ pub trait Interpret {
#[derive(Debug, new)]
pub struct Outcome {
status: InterpretStatus,
message: String,
pub status: InterpretStatus,
pub message: String,
}
impl Outcome {
@ -52,7 +54,7 @@ impl Outcome {
}
}
#[derive(Debug, Clone)]
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum InterpretStatus {
SUCCESS,
FAILURE,

View File

@ -1,15 +1,30 @@
use crate::executors::ExecutorError;
use super::{IpAddress, LogicalHost};
use std::{net::SocketAddr, str::FromStr};
use async_trait::async_trait;
use log::debug;
use super::{IpAddress, LogicalHost};
use crate::executors::ExecutorError;
#[async_trait]
pub trait LoadBalancer: Send + Sync {
fn add_backend(&mut self, backend: Backend) -> Result<(), ExecutorError>;
fn remove_backend(&mut self, backend_id: &str) -> Result<(), ExecutorError>;
fn add_frontend(&mut self, frontend: Frontend) -> Result<(), ExecutorError>;
fn remove_frontend(&mut self, frontend_id: &str) -> Result<(), ExecutorError>;
fn list_backends(&self) -> Vec<Backend>;
fn list_frontends(&self) -> Vec<Frontend>;
fn get_ip(&self) -> IpAddress;
fn get_host(&self) -> LogicalHost;
async fn add_service(&self, service: &LoadBalancerService) -> Result<(), ExecutorError>;
async fn remove_service(&self, service: &LoadBalancerService) -> Result<(), ExecutorError>;
async fn list_services(&self) -> Vec<LoadBalancerService>;
async fn ensure_initialized(&self) -> Result<(), ExecutorError>;
async fn commit_config(&self) -> Result<(), ExecutorError>;
async fn ensure_service_exists(
&self,
service: &LoadBalancerService,
) -> Result<(), ExecutorError> {
debug!("Listing haproxy services {:?}", self.list_services().await);
if !self.list_services().await.contains(service) {
self.add_service(service).await?;
}
Ok(())
}
}
impl std::fmt::Debug for dyn LoadBalancer {
@ -17,19 +32,69 @@ impl std::fmt::Debug for dyn LoadBalancer {
f.write_fmt(format_args!("LoadBalancer {}", self.get_ip()))
}
}
#[derive(Clone, Debug)]
pub struct Backend {
pub id: String,
pub ip: IpAddress,
pub port: u16,
pub weight: u8,
#[derive(Debug, PartialEq)]
pub struct LoadBalancerService {
pub backend_servers: Vec<BackendServer>,
pub listening_port: SocketAddr,
pub health_check: Option<HealthCheck>,
}
#[derive(Clone, Debug)]
pub struct Frontend {
pub id: String,
pub ip: IpAddress,
#[derive(Debug, PartialEq)]
pub struct BackendServer {
pub address: String,
pub port: u16,
pub backend_ids: Vec<String>,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HttpMethod {
GET,
POST,
PUT,
PATCH,
DELETE,
}
impl From<String> for HttpMethod {
fn from(value: String) -> Self {
Self::from_str(&value).unwrap()
}
}
impl FromStr for HttpMethod {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"GET" => Ok(HttpMethod::GET),
"POST" => Ok(HttpMethod::POST),
"PUT" => Ok(HttpMethod::PUT),
"PATCH" => Ok(HttpMethod::PATCH),
"DELETE" => Ok(HttpMethod::DELETE),
_ => Err(format!("Invalid HTTP method: {}", s)),
}
}
}
impl std::fmt::Display for HttpMethod {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
HttpMethod::GET => write!(f, "GET"),
HttpMethod::POST => write!(f, "POST"),
HttpMethod::PUT => write!(f, "PUT"),
HttpMethod::PATCH => write!(f, "PATCH"),
HttpMethod::DELETE => write!(f, "DELETE"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum HttpStatusCode {
Success2xx,
UserError4xx,
ServerError5xx,
}
#[derive(Debug, Clone, PartialEq)]
pub enum HealthCheck {
HTTP(String, HttpMethod, HttpStatusCode),
TCP(Option<u16>),
}

View File

@ -46,6 +46,7 @@ pub trait DhcpServer: Send + Sync {
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>;
async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError>;
fn get_ip(&self) -> IpAddress;
fn get_host(&self) -> LogicalHost;
async fn commit_config(&self) -> Result<(), ExecutorError>;

View File

@ -0,0 +1,372 @@
use log::{debug, info, warn};
use opnsense_config::Config;
use opnsense_config_xml::{Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer};
use uuid::Uuid;
use crate::topology::{
BackendServer, HealthCheck, HttpMethod, HttpStatusCode, LoadBalancerService,
};
pub(crate) fn haproxy_xml_config_to_harmony_loadbalancer(
haproxy: &Option<HAProxy>,
) -> Vec<LoadBalancerService> {
let haproxy = match haproxy {
Some(haproxy) => haproxy,
None => return vec![],
};
haproxy
.frontends
.frontend
.iter()
.map(|frontend| {
let mut backend_servers = vec![];
let matching_backend = haproxy
.backends
.backends
.iter()
.find(|b| b.uuid == frontend.default_backend);
let mut health_check = None;
match matching_backend {
Some(backend) => {
backend_servers.append(&mut get_servers_for_backend(backend, haproxy));
health_check = get_health_check_for_backend(backend, haproxy);
}
None => {
warn!(
"HAProxy config could not find a matching backend for frontend {:?}",
frontend
);
}
}
LoadBalancerService {
backend_servers,
listening_port: frontend.bind.parse().expect(&format!(
"HAProxy frontend address should be a valid SocketAddr, got {}",
frontend.bind
)),
health_check,
}
})
.collect()
}
pub(crate) fn get_servers_for_backend(
backend: &HAProxyBackend,
haproxy: &HAProxy,
) -> Vec<BackendServer> {
let backend_servers: Vec<&str> = match &backend.linked_servers.content {
Some(linked_servers) => linked_servers.split(',').collect(),
None => {
info!("No server defined for HAProxy backend {:?}", backend);
return vec![];
}
};
haproxy
.servers
.servers
.iter()
.filter_map(|server| {
if backend_servers.contains(&server.uuid.as_str()) {
return Some(BackendServer {
address: server.address.clone(),
port: server.port,
});
}
None
})
.collect()
}
pub(crate) fn get_health_check_for_backend(
backend: &HAProxyBackend,
haproxy: &HAProxy,
) -> Option<HealthCheck> {
let health_check_uuid = match &backend.health_check.content {
Some(uuid) => uuid,
None => return None,
};
let haproxy_health_check = match haproxy
.healthchecks
.healthchecks
.iter()
.find(|h| &h.uuid == health_check_uuid)
{
Some(health_check) => health_check,
None => return None,
};
let binding = haproxy_health_check.health_check_type.to_uppercase();
let uppercase = binding.as_str();
match uppercase {
"TCP" => {
if let Some(checkport) = haproxy_health_check.checkport.content.as_ref() {
if checkport.len() > 0 {
return Some(HealthCheck::TCP(Some(checkport.parse().expect(&format!(
"HAProxy check port should be a valid port number, got {checkport}"
)))));
}
}
return Some(HealthCheck::TCP(None));
}
"HTTP" => {
let path: String = haproxy_health_check
.http_uri
.content
.clone()
.unwrap_or_default();
let method: HttpMethod = haproxy_health_check
.http_method
.content
.clone()
.unwrap_or_default()
.into();
let status_code: HttpStatusCode = HttpStatusCode::Success2xx;
Some(HealthCheck::HTTP(path, method, status_code))
}
_ => panic!("Received unsupported health check type {}", uppercase),
}
}
pub(crate) fn harmony_load_balancer_service_to_haproxy_xml(
service: &LoadBalancerService,
) -> (
Frontend,
HAProxyBackend,
Vec<HAProxyServer>,
Option<HAProxyHealthCheck>,
) {
// Here we have to build :
// One frontend
// One backend
// One Option<healthcheck>
// Vec of servers
//
// Then merge then with haproxy config individually
//
// We also have to take into account that it is entirely possible that a backe uses a server
// with the same definition as in another backend. So when creating a new backend, we must not
// blindly create new servers because the backend does not exist yet. Even if it is a new
// backend, it may very well reuse existing servers
//
// Also we need to support router integration for port forwarding on WAN as a strategy to
// handle dyndns
// server is standalone
// backend points on server
// backend points to health check
// frontend points to backend
let healthcheck = if let Some(health_check) = &service.health_check {
match health_check {
HealthCheck::HTTP(path, http_method, http_status_code) => {
let haproxy_check = HAProxyHealthCheck {
name: format!("HTTP_{http_method}_{path}"),
uuid: Uuid::new_v4().to_string(),
http_method: http_method.to_string().into(),
health_check_type: "http".to_string(),
http_uri: path.clone().into(),
interval: "2s".to_string(),
..Default::default()
};
Some(haproxy_check)
}
HealthCheck::TCP(port) => {
let (port, port_name) = match port {
Some(port) => (Some(port.to_string()), port.to_string()),
None => (None, "serverport".to_string()),
};
let haproxy_check = HAProxyHealthCheck {
name: format!("TCP_{port_name}"),
uuid: Uuid::new_v4().to_string(),
health_check_type: "tcp".to_string(),
checkport: port.into(),
interval: "2s".to_string(),
..Default::default()
};
Some(haproxy_check)
}
}
} else {
None
};
debug!("Built healthcheck {healthcheck:?}");
let servers: Vec<HAProxyServer> = service
.backend_servers
.iter()
.map(server_to_haproxy_server)
.collect();
debug!("Built servers {servers:?}");
let mut backend = HAProxyBackend {
uuid: Uuid::new_v4().to_string(),
enabled: 1,
name: format!("backend_{}", service.listening_port),
algorithm: "roundrobin".to_string(),
random_draws: Some(2),
stickiness_expire: "30m".to_string(),
stickiness_size: "50k".to_string(),
stickiness_conn_rate_period: "10s".to_string(),
stickiness_sess_rate_period: "10s".to_string(),
stickiness_http_req_rate_period: "10s".to_string(),
stickiness_http_err_rate_period: "10s".to_string(),
stickiness_bytes_in_rate_period: "1m".to_string(),
stickiness_bytes_out_rate_period: "1m".to_string(),
mode: "tcp".to_string(), // TODO do not depend on health check here
..Default::default()
};
info!("HAPRoxy backend algorithm is currently hardcoded to roundrobin");
if let Some(hcheck) = &healthcheck {
backend.health_check_enabled = 1;
backend.health_check = hcheck.uuid.clone().into();
}
backend.linked_servers = servers
.iter()
.map(|s| s.uuid.as_str())
.collect::<Vec<&str>>()
.join(",")
.into();
debug!("Built backend {backend:?}");
let frontend = Frontend {
uuid: uuid::Uuid::new_v4().to_string(),
enabled: 1,
name: format!("frontend_{}", service.listening_port),
bind: service.listening_port.to_string(),
mode: "tcp".to_string(), // TODO do not depend on health check here
default_backend: backend.uuid.clone(),
..Default::default()
};
info!("HAPRoxy frontend and backend mode currently hardcoded to tcp");
debug!("Built frontend {frontend:?}");
(frontend, backend, servers, healthcheck)
}
fn server_to_haproxy_server(server: &BackendServer) -> HAProxyServer {
HAProxyServer {
uuid: Uuid::new_v4().to_string(),
name: format!("{}_{}", &server.address, &server.port),
enabled: 1,
address: server.address.clone(),
port: server.port,
mode: "active".to_string(),
server_type: "static".to_string(),
..Default::default()
}
}
#[cfg(test)]
mod tests {
use opnsense_config_xml::HAProxyServer;
use super::*;
#[test]
fn test_get_servers_for_backend_with_linked_servers() {
// Create a backend with linked servers
let mut backend = HAProxyBackend::default();
backend.linked_servers.content = Some("server1,server2".to_string());
// Create an HAProxy instance with servers
let mut haproxy = HAProxy::default();
let mut server = HAProxyServer::default();
server.uuid = "server1".to_string();
server.address = "192.168.1.1".to_string();
server.port = 80;
haproxy.servers.servers.push(server);
let mut server = HAProxyServer::default();
server.uuid = "server3".to_string();
server.address = "192.168.1.3".to_string();
server.port = 8080;
// Call the function
let result = get_servers_for_backend(&backend, &haproxy);
// Check the result
assert_eq!(
result,
vec![BackendServer {
address: "192.168.1.1".to_string(),
port: 80,
},]
);
}
#[test]
fn test_get_servers_for_backend_no_linked_servers() {
// Create a backend with no linked servers
let backend = HAProxyBackend::default();
// Create an HAProxy instance with servers
let mut haproxy = HAProxy::default();
let mut server = HAProxyServer::default();
server.uuid = "server1".to_string();
server.address = "192.168.1.1".to_string();
server.port = 80;
haproxy.servers.servers.push(server);
// Call the function
let result = get_servers_for_backend(&backend, &haproxy);
// Check the result
assert_eq!(result, vec![]);
}
#[test]
fn test_get_servers_for_backend_no_matching_servers() {
// Create a backend with linked servers that do not match any in HAProxy
let mut backend = HAProxyBackend::default();
backend.linked_servers.content = Some("server4,server5".to_string());
// Create an HAProxy instance with servers
let mut haproxy = HAProxy::default();
let mut server = HAProxyServer::default();
server.uuid = "server1".to_string();
server.address = "192.168.1.1".to_string();
server.port = 80;
haproxy.servers.servers.push(server);
// Call the function
let result = get_servers_for_backend(&backend, &haproxy);
// Check the result
assert_eq!(result, vec![]);
}
#[test]
fn test_get_servers_for_backend_multiple_linked_servers() {
// Create a backend with multiple linked servers
let mut backend = HAProxyBackend::default();
backend.linked_servers.content = Some("server1,server2".to_string());
// Create an HAProxy instance with matching servers
let mut haproxy = HAProxy::default();
let mut server = HAProxyServer::default();
server.uuid = "server1".to_string();
server.address = "some-hostname.test.mcd".to_string();
server.port = 80;
haproxy.servers.servers.push(server);
let mut server = HAProxyServer::default();
server.uuid = "server2".to_string();
server.address = "192.168.1.2".to_string();
server.port = 8080;
haproxy.servers.servers.push(server);
// Call the function
let result = get_servers_for_backend(&backend, &haproxy);
// Check the result
assert_eq!(
result,
vec![
BackendServer {
address: "some-hostname.test.mcd".to_string(),
port: 80,
},
BackendServer {
address: "192.168.1.2".to_string(),
port: 8080,
},
]
);
}
}

View File

@ -1,7 +1,11 @@
mod haproxy;
mod management;
use std::sync::Arc;
use async_trait::async_trait;
use haproxy::{
haproxy_xml_config_to_harmony_loadbalancer, harmony_load_balancer_service_to_haproxy_xml,
};
use log::debug;
pub use management::*;
use opnsense_config_xml::Host;
@ -10,8 +14,8 @@ use tokio::sync::RwLock;
use crate::{
executors::ExecutorError,
topology::{
Backend, DHCPStaticEntry, DhcpServer, DnsRecord, DnsServer, Firewall, FirewallRule,
Frontend, IpAddress, LoadBalancer, LogicalHost,
DHCPStaticEntry, DhcpServer, DnsRecord, DnsServer, Firewall, FirewallRule, IpAddress,
LoadBalancer, LoadBalancerService, LogicalHost,
},
};
@ -29,18 +33,28 @@ impl OPNSenseFirewall {
pub async fn new(
host: LogicalHost,
port: Option<u16>,
cluster_nic_name: &str,
username: &str,
password: &str,
) -> Self {
Self {
opnsense_config: Arc::new(RwLock::new(
opnsense_config::Config::from_credentials(host.ip, username, password).await,
opnsense_config::Config::from_credentials(host.ip, port, username, password).await,
)),
host,
cluster_nic_name: cluster_nic_name.into(),
}
}
async fn commit_config(&self) -> Result<(), ExecutorError> {
self.opnsense_config
.read()
.await
.apply()
.await
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))
}
}
impl Firewall for OPNSenseFirewall {
@ -64,48 +78,68 @@ impl Firewall for OPNSenseFirewall {
}
}
#[async_trait]
impl LoadBalancer for OPNSenseFirewall {
fn add_backend(&mut self, _backend: Backend) -> Result<(), ExecutorError> {
todo!()
}
fn remove_backend(&mut self, _backend_id: &str) -> Result<(), ExecutorError> {
todo!()
}
fn add_frontend(&mut self, _frontend: Frontend) -> Result<(), ExecutorError> {
todo!()
}
fn remove_frontend(&mut self, _frontend_id: &str) -> Result<(), ExecutorError> {
todo!()
}
fn list_backends(&self) -> Vec<Backend> {
todo!()
}
fn list_frontends(&self) -> Vec<Frontend> {
todo!()
}
fn get_ip(&self) -> IpAddress {
OPNSenseFirewall::get_ip(self)
}
fn get_host(&self) -> LogicalHost {
self.host.clone()
}
async fn add_service(&self, service: &LoadBalancerService) -> Result<(), ExecutorError> {
let mut config = self.opnsense_config.write().await;
let (frontend, backend, servers, healthcheck) =
harmony_load_balancer_service_to_haproxy_xml(service);
let mut load_balancer = config.load_balancer();
load_balancer.add_backend(backend);
load_balancer.add_frontend(frontend);
load_balancer.add_servers(servers);
if let Some(healthcheck) = healthcheck {
load_balancer.add_healthcheck(healthcheck);
}
Ok(())
}
async fn remove_service(&self, service: &LoadBalancerService) -> Result<(), ExecutorError> {
todo!()
}
async fn commit_config(&self) -> Result<(), ExecutorError> {
OPNSenseFirewall::commit_config(self).await?;
todo!("Make sure load balancer is reloaded properly")
}
async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
let mut config = self.opnsense_config.write().await;
let load_balancer = config.load_balancer();
if let Some(_) = load_balancer.get_full_config() {
debug!("HAProxy config available in opnsense config, assuming it is already installed");
return Ok(());
}
config.install_package("os-haproxy").await.map_err(|e| {
ExecutorError::UnexpectedError(format!(
"Executor failed when trying to install os-haproxy package with error {e:?}"
))
})?;
config.load_balancer().enable(true);
Ok(())
}
async fn list_services(&self) -> Vec<LoadBalancerService> {
let mut config = self.opnsense_config.write().await;
let load_balancer = config.load_balancer();
let haproxy_xml_config = load_balancer.get_full_config();
haproxy_xml_config_to_harmony_loadbalancer(haproxy_xml_config)
}
}
#[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()))
OPNSenseFirewall::commit_config(self).await
}
async fn add_static_mapping(&self, entry: &DHCPStaticEntry) -> Result<(), ExecutorError> {
@ -155,6 +189,16 @@ impl DhcpServer for OPNSenseFirewall {
Ok(())
}
async fn set_boot_filename(&self, boot_filename: &str) -> Result<(), ExecutorError> {
{
let mut writable_opnsense = self.opnsense_config.write().await;
writable_opnsense.dhcp().set_boot_filename(boot_filename);
debug!("OPNsense dhcp server set boot filename {boot_filename}");
}
Ok(())
}
}
#[async_trait]

View File

@ -20,6 +20,7 @@ use crate::domain::score::Score;
pub struct DhcpScore {
host_binding: Vec<HostBinding>,
next_server: Option<IpAddress>,
boot_filename: Option<String>,
}
impl Score for DhcpScore {
@ -98,22 +99,48 @@ impl DhcpInterpret {
))
}
async fn set_next_server(
async fn set_pxe_options(
&self,
_inventory: &Inventory,
topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> {
match self.score.next_server {
let next_server_outcome = 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(
Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret Set next boot to {next_server}"),
))
)
}
None => Ok(Outcome::noop()),
None => Outcome::noop(),
};
let boot_filename_outcome = match &self.score.boot_filename {
Some(boot_filename) => {
let dhcp_server = Arc::new(topology.dhcp_server.clone());
dhcp_server.set_boot_filename(&boot_filename).await?;
Outcome::new(
InterpretStatus::SUCCESS,
format!("Dhcp Interpret Set boot filename to {boot_filename}"),
)
}
None => Outcome::noop(),
};
if next_server_outcome.status == InterpretStatus::NOOP
&& boot_filename_outcome.status == InterpretStatus::NOOP
{
return Ok(Outcome::noop());
}
Ok(Outcome::new(
InterpretStatus::SUCCESS,
format!(
"Dhcp Interpret Set next boot to {:?} and boot_filename to {:?}",
self.score.boot_filename, self.score.boot_filename
),
))
}
}
@ -142,9 +169,9 @@ impl Interpret for DhcpInterpret {
) -> Result<Outcome, InterpretError> {
info!("Executing {} on inventory {inventory:?}", self.get_name());
self.set_next_server(inventory, topology).await?;
self.set_pxe_options(inventory, topology).await?;
// self.add_static_entries(inventory, topology).await?;
self.add_static_entries(inventory, topology).await?;
topology.dhcp_server.commit_config().await?;

View File

@ -0,0 +1,82 @@
use async_trait::async_trait;
use log::info;
use crate::{
data::{Id, Version},
interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome},
inventory::Inventory,
score::Score,
topology::{HAClusterTopology, LoadBalancerService},
};
#[derive(Debug)]
pub struct LoadBalancerScore {
pub public_services: Vec<LoadBalancerService>,
pub private_services: Vec<LoadBalancerService>,
// TODO public and private services are likely wrong, should be a single list of
// (listen_interface, LoadBalancerService) tuples or something like that
// I am not sure what to use as listen_interface, should it be interface name, ip address,
// uuid?
}
impl Score for LoadBalancerScore {
type InterpretType = LoadBalancerInterpret;
fn create_interpret(self) -> Self::InterpretType {
LoadBalancerInterpret::new(self)
}
}
#[derive(Debug)]
pub struct LoadBalancerInterpret {
version: Version,
status: InterpretStatus,
score: LoadBalancerScore,
}
impl LoadBalancerInterpret {
pub fn new(score: LoadBalancerScore) -> Self {
Self {
version: Version::from("1.0.0").expect("Version should be valid"),
status: InterpretStatus::QUEUED,
score,
}
}
}
#[async_trait]
impl Interpret for LoadBalancerInterpret {
async fn execute(
&self,
inventory: &Inventory,
topology: &HAClusterTopology,
) -> Result<Outcome, InterpretError> {
topology.load_balancer.ensure_initialized().await?;
for service in self.score.public_services.iter() {
info!("Ensuring service exists {service:?}");
topology.load_balancer.ensure_service_exists(service).await?;
}
for service in self.score.private_services.iter() {
info!("Ensuring private service exists {service:?}");
topology.load_balancer.ensure_service_exists(service).await?;
}
info!("Applying load balancer configuration");
topology.load_balancer.commit_config().await?;
todo!()
}
fn get_name(&self) -> InterpretName {
InterpretName::LoadBalancer
}
fn get_version(&self) -> Version {
self.version.clone()
}
fn get_status(&self) -> InterpretStatus {
self.status.clone()
}
fn get_children(&self) -> Vec<Id> {
todo!()
}
}

View File

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

View File

@ -31,6 +31,7 @@ impl OKDBootstrapDhcpScore {
// 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()),
Some("bootx64.efi".to_string()),
),
}
}

View File

@ -0,0 +1,88 @@
use std::net::SocketAddr;
use crate::{
modules::load_balancer::LoadBalancerScore,
score::Score,
topology::{
BackendServer, HAClusterTopology, HealthCheck, HttpMethod, HttpStatusCode,
LoadBalancerService,
},
};
#[derive(Debug)]
pub struct OKDLoadBalancerScore {
load_balancer_score: LoadBalancerScore,
}
impl OKDLoadBalancerScore {
pub fn new(topology: &HAClusterTopology) -> Self {
let public_ip = topology.router.get_gateway();
let public_services = vec![
LoadBalancerService {
backend_servers: Self::control_plane_to_backend_server(topology, 80),
listening_port: SocketAddr::new(public_ip, 80),
health_check: Some(HealthCheck::TCP(None)),
},
LoadBalancerService {
backend_servers: Self::control_plane_to_backend_server(topology, 443),
listening_port: SocketAddr::new(public_ip, 443),
health_check: Some(HealthCheck::TCP(None)),
},
];
let private_services = vec![
LoadBalancerService {
backend_servers: Self::control_plane_to_backend_server(topology, 80),
listening_port: SocketAddr::new(public_ip, 80),
health_check: Some(HealthCheck::TCP(None)),
},
LoadBalancerService {
backend_servers: Self::control_plane_to_backend_server(topology, 443),
listening_port: SocketAddr::new(public_ip, 443),
health_check: Some(HealthCheck::TCP(None)),
},
LoadBalancerService {
backend_servers: Self::control_plane_to_backend_server(topology, 22623),
listening_port: SocketAddr::new(public_ip, 22623),
health_check: Some(HealthCheck::TCP(None)),
},
LoadBalancerService {
backend_servers: Self::control_plane_to_backend_server(topology, 6443),
listening_port: SocketAddr::new(public_ip, 6443),
health_check: Some(HealthCheck::HTTP(
"/readyz".to_string(),
HttpMethod::GET,
HttpStatusCode::Success2xx,
)),
},
];
Self {
load_balancer_score: LoadBalancerScore {
public_services,
private_services,
},
}
}
fn control_plane_to_backend_server(
topology: &HAClusterTopology,
port: u16,
) -> Vec<BackendServer> {
topology
.control_plane
.iter()
.map(|cp| BackendServer {
address: cp.ip.to_string(),
port,
})
.collect()
}
}
impl Score for OKDLoadBalancerScore {
type InterpretType = <LoadBalancerScore as Score>::InterpretType;
fn create_interpret(self) -> Self::InterpretType {
self.load_balancer_score.create_interpret()
}
}

View File

@ -1,3 +1,4 @@
pub mod dhcp;
pub mod dns;
pub mod load_balancer;

View File

@ -18,7 +18,7 @@ pub fn ip(input: TokenStream) -> TokenStream {
if let Ok(_) = ip_str.parse::<std::net::Ipv6Addr>() {
let expanded =
quote! { std::net::IpAddr::V4(#ip_str.parse::<std::net::Ipv6Addr>().unwrap()) };
quote! { std::net::IpAddr::V6(#ip_str.parse::<std::net::Ipv6Addr>().unwrap()) };
return TokenStream::from(expanded);
}

View File

@ -17,14 +17,8 @@ xml-rs = "0.8"
thiserror = "1.0"
async-trait = { workspace = true }
tokio = { workspace = true }
[dependencies.uuid]
version = "1.11.0"
features = [
"v4", # Lets you generate random UUIDs
"fast-rng", # Use a faster (but still sufficiently random) RNG
"macro-diagnostics", # Enable better diagnostics for compile-time UUIDs
]
uuid = { workspace = true }
rand = { workspace = true }
[dev-dependencies]
pretty_assertions = "1.4.1"

View File

@ -18,6 +18,7 @@ pub struct DhcpInterface {
pub domain: Option<MaybeString>,
pub netboot: Option<u32>,
pub nextserver: Option<String>,
pub filename64: Option<String>,
#[yaserde(rename = "ddnsdomainalgorithm")]
pub ddns_domain_algorithm: Option<MaybeString>,
#[yaserde(rename = "numberoptions")]

View File

@ -0,0 +1,635 @@
use rand;
use rand::Rng;
use xml::reader::XmlEvent as ReadEvent;
use xml::writer::XmlEvent as WriteEvent;
use yaserde::MaybeString;
use yaserde::{YaDeserialize as YaDeserializeTrait, YaSerialize as YaSerializeTrait};
use yaserde_derive::{YaDeserialize, YaSerialize};
impl YaDeserializeTrait for HAProxyId {
fn deserialize<R: std::io::Read>(reader: &mut yaserde::de::Deserializer<R>) -> Result<Self, String> {
let field_name = match reader.peek()? {
ReadEvent::StartElement {
name, attributes, ..
} => {
if attributes.len() > 0 {
return Err(String::from(
"Attributes not currently supported by HAProxyId",
));
}
name.local_name.clone()
}
_ => return Err(String::from("Unsupporte ReadEvent type")),
};
reader.next_event()?;
let content = match reader.peek()? {
ReadEvent::Characters(content) => content.clone(),
ReadEvent::EndElement { name } => String::new(),
_ => return Err(String::from("Unsupporte ReadEvent type")),
};
Ok(Self(content))
}
}
impl YaSerializeTrait for HAProxyId {
fn serialize<W: std::io::Write>(
&self,
writer: &mut yaserde::ser::Serializer<W>,
) -> Result<(), String> {
let yaserde_label = writer.get_start_event_name();
match yaserde_label {
Some(label) => {
let struct_start_event = xml::writer::XmlEvent::start_element(label.as_ref());
writer
.write(struct_start_event)
.map_err(|_e| format!("Start element {label:?} write failed"))?;
}
None => return Err("HAPRoxyId must have a label preset in the writer".to_string()),
};
writer
.write(WriteEvent::characters(&self.0))
.expect("Writer failed");
writer
.write(WriteEvent::end_element())
.expect("Writer failed");
Ok(())
}
fn serialize_attributes(
&self,
attributes: Vec<xml::attribute::OwnedAttribute>,
namespace: xml::namespace::Namespace,
) -> Result<
(
Vec<xml::attribute::OwnedAttribute>,
xml::namespace::Namespace,
),
String,
> {
todo!()
}
}
#[derive(PartialEq, Debug)]
pub struct HAProxyId(String);
impl Default for HAProxyId {
fn default() -> Self {
let mut rng = rand::thread_rng();
Self(format!("{:x}.{:x}", rng.gen::<u64>(), rng.gen::<u32>()))
}
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "HAProxy")]
pub struct HAProxy {
#[yaserde(attribute)]
pub version: String,
pub general: HaProxyGeneral,
pub frontends: HAProxyFrontends,
pub backends: HAProxyBackends,
pub servers: HAProxyServers,
pub healthchecks: HAProxyHealthChecks,
pub acls: MaybeString,
pub actions: MaybeString,
pub luas: MaybeString,
pub fcgis: MaybeString,
pub errorfiles: MaybeString,
pub mapfiles: MaybeString,
pub groups: MaybeString,
pub users: MaybeString,
pub cpus: MaybeString,
pub resolvers: MaybeString,
pub mailers: MaybeString,
pub maintenance: Maintenance,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Maintenance {
#[yaserde(rename = "cronjobs")]
pub cronjobs: CronJobs,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct CronJobs {
#[yaserde(rename = "syncCerts")]
pub sync_certs: u32,
#[yaserde(rename = "syncCertsCron")]
pub sync_certs_cron: MaybeString,
#[yaserde(rename = "updateOcsp")]
pub update_ocsp: u32,
#[yaserde(rename = "updateOcspCron")]
pub update_ocsp_cron: MaybeString,
#[yaserde(rename = "reloadService")]
pub reload_service: u32,
#[yaserde(rename = "reloadServiceCron")]
pub reload_service_cron: MaybeString,
#[yaserde(rename = "restartService")]
pub restart_service: u32,
#[yaserde(rename = "restartServiceCron")]
pub restart_service_cron: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyGeneral {
pub enabled: i32,
#[yaserde(rename = "gracefulStop")]
pub graceful_stop: i32,
#[yaserde(rename = "hardStopAfter")]
pub hard_stop_after: String,
#[yaserde(rename = "closeSpreadTime")]
pub close_spread_time: MaybeString,
#[yaserde(rename = "seamlessReload")]
pub seamless_reload: i32,
#[yaserde(rename = "storeOcsp")]
pub store_ocsp: i32,
#[yaserde(rename = "showIntro")]
pub show_intro: i32,
pub peers: Peers,
pub tuning: Tuning,
pub defaults: HaProxyDefaults,
pub logging: HaProxyLogging,
pub stats: Stats,
pub cache: HaProxyCache,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Peers {
pub enabled: i32,
pub name1: MaybeString,
pub listen1: MaybeString,
pub port1: i32,
pub name2: MaybeString,
pub listen2: MaybeString,
pub port2: i32,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Tuning {
pub root: i32,
#[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")]
pub max_dh_size: i32,
#[yaserde(rename = "bufferSize")]
pub buffer_size: i32,
#[yaserde(rename = "spreadChecks")]
pub spread_checks: i32,
#[yaserde(rename = "bogusProxyEnabled")]
pub bogus_proxy_enabled: i32,
#[yaserde(rename = "luaMaxMem")]
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")]
pub ssl_bind_options: MaybeString,
#[yaserde(rename = "ssl_minVersion")]
pub ssl_min_version: MaybeString,
#[yaserde(rename = "ssl_maxVersion")]
pub ssl_max_version: MaybeString,
#[yaserde(rename = "ssl_cipherList")]
pub ssl_cipher_list: MaybeString,
#[yaserde(rename = "ssl_cipherSuites")]
pub ssl_cipher_suites: MaybeString,
#[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)]
pub struct HaProxyDefaults {
#[yaserde(rename = "maxConnections")]
pub max_connections: MaybeString,
#[yaserde(rename = "maxConnectionsServers")]
pub max_connections_servers: MaybeString,
#[yaserde(rename = "timeoutClient")]
pub timeout_client: String,
#[yaserde(rename = "timeoutConnect")]
pub timeout_connect: String,
#[yaserde(rename = "timeoutCheck")]
pub timeout_check: MaybeString,
#[yaserde(rename = "timeoutServer")]
pub timeout_server: String,
pub retries: i32,
pub redispatch: String,
pub init_addr: String,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyLogging {
pub host: String,
pub facility: String,
pub level: String,
pub length: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Stats {
pub enabled: i32,
pub port: i32,
#[yaserde(rename = "remoteEnabled")]
pub remote_enabled: i32,
#[yaserde(rename = "remoteBind")]
pub remote_bind: MaybeString,
#[yaserde(rename = "authEnabled")]
pub auth_enabled: i32,
#[yaserde(rename = "users")]
pub users: MaybeString,
#[yaserde(rename = "allowedUsers")]
pub allowed_users: MaybeString,
#[yaserde(rename = "allowedGroups")]
pub allowed_groups: MaybeString,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
pub prometheus_enabled: i32,
pub prometheus_bind: String,
pub prometheus_path: String,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyCache {
pub enabled: i32,
#[yaserde(rename = "totalMaxSize")]
pub total_max_size: i32,
#[yaserde(rename = "maxAge")]
pub max_age: i32,
#[yaserde(rename = "maxObjectSize")]
pub max_object_size: MaybeString,
#[yaserde(rename = "processVary")]
pub process_vary: i32,
#[yaserde(rename = "maxSecondaryEntries")]
pub max_secondary_entries: i32,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyFrontends {
pub frontend: Vec<Frontend>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Frontend {
#[yaserde(attribute)]
pub uuid: String,
pub id: HAProxyId,
pub enabled: i32,
pub name: String,
pub description: MaybeString,
pub bind: String,
#[yaserde(rename = "bindOptions")]
pub bind_options: MaybeString,
pub mode: String,
#[yaserde(rename = "defaultBackend")]
pub default_backend: String,
pub ssl_enabled: i32,
pub ssl_certificates: MaybeString,
pub ssl_default_certificate: MaybeString,
#[yaserde(rename = "ssl_customOptions")]
pub ssl_custom_options: MaybeString,
#[yaserde(rename = "ssl_advancedEnabled")]
pub ssl_advanced_enabled: i32,
#[yaserde(rename = "ssl_bindOptions")]
pub ssl_bind_options: MaybeString,
#[yaserde(rename = "ssl_minVersion")]
pub ssl_min_version: MaybeString,
#[yaserde(rename = "ssl_maxVersion")]
pub ssl_max_version: MaybeString,
#[yaserde(rename = "ssl_cipherList")]
pub ssl_cipher_list: MaybeString,
#[yaserde(rename = "ssl_cipherSuites")]
pub ssl_cipher_suites: MaybeString,
#[yaserde(rename = "ssl_hstsEnabled")]
pub ssl_hsts_enabled: i32,
#[yaserde(rename = "ssl_hstsIncludeSubDomains")]
pub ssl_hsts_include_sub_domains: i32,
#[yaserde(rename = "ssl_hstsPreload")]
pub ssl_hsts_preload: i32,
#[yaserde(rename = "ssl_hstsMaxAge")]
pub ssl_hsts_max_age: i32,
#[yaserde(rename = "ssl_clientAuthEnabled")]
pub ssl_client_auth_enabled: i32,
#[yaserde(rename = "ssl_clientAuthVerify")]
pub ssl_client_auth_verify: MaybeString,
#[yaserde(rename = "ssl_clientAuthCAs")]
pub ssl_client_auth_cas: MaybeString,
#[yaserde(rename = "ssl_clientAuthCRLs")]
pub ssl_client_auth_cr_ls: MaybeString,
#[yaserde(rename = "basicAuthEnabled")]
pub basic_auth_enabled: i32,
#[yaserde(rename = "basicAuthUsers")]
pub basic_auth_users: MaybeString,
#[yaserde(rename = "basicAuthGroups")]
pub basic_auth_groups: MaybeString,
#[yaserde(rename = "tuning_maxConnections")]
pub tuning_max_connections: MaybeString,
#[yaserde(rename = "tuning_timeoutClient")]
pub tuning_timeout_client: MaybeString,
#[yaserde(rename = "tuning_timeoutHttpReq")]
pub tuning_timeout_http_req: MaybeString,
#[yaserde(rename = "tuning_timeoutHttpKeepAlive")]
pub tuning_timeout_http_keep_alive: MaybeString,
#[yaserde(rename = "linkedCpuAffinityRules")]
pub linked_cpu_affinity_rules: MaybeString,
pub tuning_shards: MaybeString,
#[yaserde(rename = "logging_dontLogNull")]
pub logging_dont_log_null: i32,
#[yaserde(rename = "logging_dontLogNormal")]
pub logging_dont_log_normal: i32,
#[yaserde(rename = "logging_logSeparateErrors")]
pub logging_log_separate_errors: i32,
#[yaserde(rename = "logging_detailedLog")]
pub logging_detailed_log: i32,
#[yaserde(rename = "logging_socketStats")]
pub logging_socket_stats: i32,
pub stickiness_pattern: MaybeString,
#[yaserde(rename = "stickiness_dataTypes")]
pub stickiness_data_types: MaybeString,
pub stickiness_expire: MaybeString,
pub stickiness_size: MaybeString,
pub stickiness_counter: i32,
pub stickiness_counter_key: MaybeString,
pub stickiness_length: MaybeString,
#[yaserde(rename = "stickiness_connRatePeriod")]
pub stickiness_conn_rate_period: MaybeString,
#[yaserde(rename = "stickiness_sessRatePeriod")]
pub stickiness_sess_rate_period: MaybeString,
#[yaserde(rename = "stickiness_httpReqRatePeriod")]
pub stickiness_http_req_rate_period: MaybeString,
#[yaserde(rename = "stickiness_httpErrRatePeriod")]
pub stickiness_http_err_rate_period: MaybeString,
#[yaserde(rename = "stickiness_bytesInRatePeriod")]
pub stickiness_bytes_in_rate_period: MaybeString,
#[yaserde(rename = "stickiness_bytesOutRatePeriod")]
pub stickiness_bytes_out_rate_period: MaybeString,
#[yaserde(rename = "http2Enabled")]
pub http2_enabled: i32,
#[yaserde(rename = "http2Enabled_nontls")]
pub http2_enabled_nontls: i32,
pub advertised_protocols: MaybeString,
#[yaserde(rename = "forwardFor")]
pub forward_for: i32,
pub prometheus_enabled: i32,
pub prometheus_path: MaybeString,
#[yaserde(rename = "connectionBehaviour")]
pub connection_behaviour: MaybeString,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
#[yaserde(rename = "linkedActions")]
pub linked_actions: MaybeString,
#[yaserde(rename = "linkedErrorfiles")]
pub linked_error_files: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyBackends {
#[yaserde(rename = "backend")]
pub backends: Vec<HAProxyBackend>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyBackend {
#[yaserde(attribute, rename = "uuid")]
pub uuid: String,
#[yaserde(rename = "id")]
pub id: HAProxyId,
#[yaserde(rename = "enabled")]
pub enabled: u8,
#[yaserde(rename = "name")]
pub name: String,
#[yaserde(rename = "description")]
pub description: MaybeString,
#[yaserde(rename = "mode")]
pub mode: String,
#[yaserde(rename = "algorithm")]
pub algorithm: String,
#[yaserde(rename = "random_draws")]
pub random_draws: Option<u32>,
#[yaserde(rename = "proxyProtocol")]
pub proxy_protocol: MaybeString,
#[yaserde(rename = "linkedServers")]
pub linked_servers: MaybeString,
#[yaserde(rename = "linkedFcgi")]
pub linked_fcgi: MaybeString,
#[yaserde(rename = "linkedResolver")]
pub linked_resolver: MaybeString,
#[yaserde(rename = "resolverOpts")]
pub resolver_opts: MaybeString,
#[yaserde(rename = "resolvePrefer")]
pub resolve_prefer: MaybeString,
#[yaserde(rename = "source")]
pub source: MaybeString,
#[yaserde(rename = "healthCheckEnabled")]
pub health_check_enabled: u8,
#[yaserde(rename = "healthCheck")]
pub health_check: MaybeString,
#[yaserde(rename = "healthCheckLogStatus")]
pub health_check_log_status: u8,
#[yaserde(rename = "checkInterval")]
pub check_interval: MaybeString,
#[yaserde(rename = "checkDownInterval")]
pub check_down_interval: MaybeString,
#[yaserde(rename = "healthCheckFall")]
pub health_check_fall: MaybeString,
#[yaserde(rename = "healthCheckRise")]
pub health_check_rise: MaybeString,
#[yaserde(rename = "linkedMailer")]
pub linked_mailer: MaybeString,
#[yaserde(rename = "http2Enabled")]
pub http2_enabled: u8,
#[yaserde(rename = "http2Enabled_nontls")]
pub http2_enabled_nontls: u8,
#[yaserde(rename = "ba_advertised_protocols")]
pub ba_advertised_protocols: MaybeString,
#[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: MaybeString,
pub persistence_cookiename: MaybeString,
pub persistence_stripquotes: u8,
pub stickiness_pattern: MaybeString,
#[yaserde(rename = "stickiness_dataTypes")]
pub stickiness_data_types: MaybeString,
pub stickiness_expire: String,
pub stickiness_size: String,
pub stickiness_cookiename: MaybeString,
pub stickiness_cookielength: MaybeString,
#[yaserde(rename = "stickiness_connRatePeriod")]
pub stickiness_conn_rate_period: String,
#[yaserde(rename = "stickiness_sessRatePeriod")]
pub stickiness_sess_rate_period: String,
#[yaserde(rename = "stickiness_httpReqRatePeriod")]
pub stickiness_http_req_rate_period: String,
#[yaserde(rename = "stickiness_httpErrRatePeriod")]
pub stickiness_http_err_rate_period: String,
#[yaserde(rename = "stickiness_bytesInRatePeriod")]
pub stickiness_bytes_in_rate_period: String,
#[yaserde(rename = "stickiness_bytesOutRatePeriod")]
pub stickiness_bytes_out_rate_period: String,
#[yaserde(rename = "basicAuthEnabled")]
pub basic_auth_enabled: u8,
#[yaserde(rename = "basicAuthUsers")]
pub basic_auth_users: MaybeString,
#[yaserde(rename = "basicAuthGroups")]
pub basic_auth_groups: MaybeString,
#[yaserde(rename = "tuning_timeoutConnect")]
pub tuning_timeout_connect: MaybeString,
#[yaserde(rename = "tuning_timeoutCheck")]
pub tuning_timeout_check: MaybeString,
#[yaserde(rename = "tuning_timeoutServer")]
pub tuning_timeout_server: MaybeString,
#[yaserde(rename = "tuning_retries")]
pub tuning_retries: MaybeString,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
#[yaserde(rename = "tuning_defaultserver")]
pub tuning_defaultserver: MaybeString,
#[yaserde(rename = "tuning_noport")]
pub tuning_noport: u8,
#[yaserde(rename = "tuning_httpreuse")]
pub tuning_httpreuse: MaybeString,
#[yaserde(rename = "tuning_caching")]
pub tuning_caching: u8,
#[yaserde(rename = "linkedActions")]
pub linked_actions: MaybeString,
#[yaserde(rename = "linkedErrorfiles")]
pub linked_errorfiles: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyServers {
#[yaserde(rename = "server")]
pub servers: Vec<HAProxyServer>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyServer {
#[yaserde(attribute, rename = "uuid")]
pub uuid: String,
pub id: HAProxyId,
pub enabled: u8,
pub name: String,
pub description: MaybeString,
pub address: String,
pub port: u16,
pub checkport: MaybeString,
pub mode: String,
pub multiplexer_protocol: MaybeString,
#[yaserde(rename = "type")]
pub server_type: String,
#[yaserde(rename = "serviceName")]
pub service_name: MaybeString,
pub number: MaybeString,
#[yaserde(rename = "linkedResolver")]
pub linked_resolver: MaybeString,
#[yaserde(rename = "resolverOpts")]
pub resolver_opts: MaybeString,
#[yaserde(rename = "resolvePrefer")]
pub resolve_prefer: MaybeString,
pub ssl: u8,
#[yaserde(rename = "sslSNI")]
pub ssl_sni: MaybeString,
#[yaserde(rename = "sslVerify")]
pub ssl_verify: u8,
#[yaserde(rename = "sslCA")]
pub ssl_ca: MaybeString,
#[yaserde(rename = "sslCRL")]
pub ssl_crl: MaybeString,
#[yaserde(rename = "sslClientCertificate")]
pub ssl_client_certificate: MaybeString,
#[yaserde(rename = "maxConnections")]
pub max_connections: MaybeString,
pub weight: Option<u32>,
#[yaserde(rename = "checkInterval")]
pub check_interval: MaybeString,
#[yaserde(rename = "checkDownInterval")]
pub check_down_interval: MaybeString,
pub source: MaybeString,
pub advanced: MaybeString,
#[yaserde(rename = "unix_socket")]
pub unix_socket: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyHealthChecks {
#[yaserde(rename = "healthcheck")]
pub healthchecks: Vec<HAProxyHealthCheck>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyHealthCheck {
#[yaserde(attribute)]
pub uuid: String,
pub name: String,
pub description: MaybeString,
#[yaserde(rename = "type")]
pub health_check_type: String,
pub interval: String,
pub ssl: MaybeString,
#[yaserde(rename = "sslSNI")]
pub ssl_sni: MaybeString,
pub force_ssl: u8,
pub checkport: MaybeString,
pub http_method: MaybeString,
pub http_uri: MaybeString,
pub http_version: MaybeString,
#[yaserde(rename = "http_host")]
pub http_host: MaybeString,
#[yaserde(rename = "http_expressionEnabled")]
pub http_expression_enabled: Option<u8>,
pub http_expression: MaybeString,
pub http_negate: MaybeString,
pub http_value: MaybeString,
pub tcp_enabled: MaybeString,
#[yaserde(rename = "tcp_sendValue")]
pub tcp_send_value: MaybeString,
#[yaserde(rename = "tcp_matchType")]
pub tcp_match_type: MaybeString,
pub tcp_negate: MaybeString,
#[yaserde(rename = "tcp_matchValue")]
pub tcp_match_value: MaybeString,
pub agent_port: MaybeString,
pub mysql_user: MaybeString,
pub mysql_post41: MaybeString,
pub pgsql_user: MaybeString,
pub smtp_domain: MaybeString,
pub esmtp_domain: MaybeString,
#[yaserde(rename = "agentPort")]
pub agent_port_uppercase: MaybeString,
#[yaserde(rename = "dbUser")]
pub db_user: MaybeString,
#[yaserde(rename = "smtpDomain")]
pub smtp_domain_uppercase: MaybeString,
}

View File

@ -28,6 +28,8 @@ pub struct Interface {
pub r#virtual: Option<MaybeString>,
pub subnet: Option<MaybeString>,
pub ipaddrv6: Option<MaybeString>,
#[yaserde(rename = "dhcp6-ia-pd-len")]
pub dhcp6_ia_pd_len: Option<MaybeString>,
pub networks: Option<MaybeString>,
pub subnetv6: Option<MaybeString>,
pub media: Option<MaybeString>,
@ -38,6 +40,33 @@ pub struct Interface {
pub track6_prefix_id: Option<MaybeString>,
#[yaserde(rename = "dhcprejectfrom")]
pub dhcprejectfrom: Option<MaybeString>,
pub adv_dhcp6_interface_statement_send_options: Option<MaybeString>,
pub adv_dhcp6_interface_statement_request_options: Option<MaybeString>,
pub adv_dhcp6_interface_statement_information_only_enable: Option<MaybeString>,
pub adv_dhcp6_interface_statement_script: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_address_enable: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_address: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_address_id: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_address_pltime: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_address_vltime: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_prefix_enable: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_prefix: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_prefix_id: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_prefix_pltime: Option<MaybeString>,
pub adv_dhcp6_id_assoc_statement_prefix_vltime: Option<MaybeString>,
pub adv_dhcp6_prefix_interface_statement_sla_len: Option<MaybeString>,
pub adv_dhcp6_authentication_statement_authname: Option<MaybeString>,
pub adv_dhcp6_authentication_statement_protocol: Option<MaybeString>,
pub adv_dhcp6_authentication_statement_algorithm: Option<MaybeString>,
pub adv_dhcp6_authentication_statement_rdm: Option<MaybeString>,
pub adv_dhcp6_key_info_statement_keyname: Option<MaybeString>,
pub adv_dhcp6_key_info_statement_realm: Option<MaybeString>,
pub adv_dhcp6_key_info_statement_keyid: Option<MaybeString>,
pub adv_dhcp6_key_info_statement_secret: Option<MaybeString>,
pub adv_dhcp6_key_info_statement_expire: Option<MaybeString>,
pub adv_dhcp6_config_advanced: Option<MaybeString>,
pub adv_dhcp6_config_file_override: Option<MaybeString>,
pub adv_dhcp6_config_file_override_path: Option<MaybeString>,
pub adv_dhcp_pt_timeout: Option<MaybeString>,
pub adv_dhcp_pt_retry: Option<MaybeString>,
pub adv_dhcp_pt_select_timeout: Option<MaybeString>,

View File

@ -1,6 +1,8 @@
mod opnsense;
mod interfaces;
mod dhcpd;
mod haproxy;
pub use haproxy::*;
pub use opnsense::*;
pub use interfaces::*;
pub use dhcpd::*;

View File

@ -1,3 +1,4 @@
use crate::HAProxy;
use crate::{data::dhcpd::DhcpInterface, xml_utils::to_xml_str};
use log::error;
use uuid::Uuid;
@ -42,6 +43,8 @@ pub struct OPNsense {
pub laggs: Laggs,
pub wireless: Wireless,
pub hasync: Hasync,
#[yaserde(rename = "Pischem")]
pub pischem: Option<RawXml>,
pub ifgroups: Ifgroups,
}
@ -106,6 +109,7 @@ pub struct Options {
pub struct Filters {
#[yaserde(rename = "rule")]
pub rules: Vec<Rule>,
pub bypassstaticroutes: Option<MaybeString>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -431,6 +435,9 @@ pub struct OPNsenseXmlSection {
#[yaserde(rename = "DHCRelay")]
pub dhcrelay: Option<RawXml>,
pub trust: Option<RawXml>,
pub tftp: Option<Tftp>,
#[yaserde(rename = "Nginx")]
pub nginx: Option<RawXml>,
pub wireguard: Option<Wireguard>,
#[yaserde(rename = "Swanctl")]
pub swanctl: Swanctl,
@ -444,6 +451,19 @@ pub struct OPNsenseXmlSection {
pub haproxy: Option<HAProxy>,
}
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
pub struct Tftp {
general: TftpGeneral,
}
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
pub struct TftpGeneral {
#[yaserde(attribute)]
version: String,
enabled: u8,
listen: String,
}
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
#[yaserde(rename = "IDS")]
pub struct IDS {
@ -509,9 +529,9 @@ pub struct IPsec {
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
pub struct GeneralIpsec {
enabled: MaybeString,
preferred_oldsa: MaybeString,
disablevpnrules: MaybeString,
passthrough_networks: MaybeString,
preferred_oldsa: Option<MaybeString>,
disablevpnrules: Option<MaybeString>,
passthrough_networks: Option<MaybeString>,
}
#[derive(Debug, YaSerialize, YaDeserialize, PartialEq)]
@ -1079,13 +1099,13 @@ pub struct UnboundGeneral {
pub port: i32,
pub stats: MaybeString,
pub active_interface: MaybeString,
pub dnssec: MaybeString,
pub dnssec: Option<MaybeString>,
pub dns64: MaybeString,
pub dns64prefix: MaybeString,
pub noarecords: MaybeString,
pub regdhcp: i8,
pub regdhcp: Option<i8>,
pub regdhcpdomain: MaybeString,
pub regdhcpstatic: i8,
pub regdhcpstatic: Option<i8>,
pub noreglladdr6: MaybeString,
pub noregrecords: MaybeString,
pub txtsupport: MaybeString,
@ -1097,22 +1117,22 @@ pub struct UnboundGeneral {
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Advanced {
pub hideidentity: i8,
pub hideversion: i8,
pub prefetch: i8,
pub prefetchkey: i8,
pub dnssecstripped: i8,
pub aggressivensec: i8,
pub serveexpired: i8,
pub hideidentity: Option<i8>,
pub hideversion: Option<i8>,
pub prefetch: Option<i8>,
pub prefetchkey: Option<i8>,
pub dnssecstripped: Option<i8>,
pub aggressivensec: Option<i8>,
pub serveexpired: Option<i8>,
pub serveexpiredreplyttl: MaybeString,
pub serveexpiredttl: MaybeString,
pub serveexpiredttlreset: i32,
pub serveexpiredttlreset: Option<i32>,
pub serveexpiredclienttimeout: MaybeString,
pub qnameminstrict: i32,
pub extendedstatistics: i32,
pub logqueries: i32,
pub logreplies: i32,
pub logtagqueryreply: i32,
pub qnameminstrict: Option<i32>,
pub extendedstatistics: Option<i32>,
pub logqueries: Option<i32>,
pub logreplies: Option<i32>,
pub logtagqueryreply: Option<i32>,
pub logservfail: MaybeString,
pub loglocalactions: MaybeString,
pub logverbosity: i32,
@ -1127,7 +1147,7 @@ pub struct Advanced {
pub numqueriesperthread: MaybeString,
pub outgoingrange: MaybeString,
pub jostletimeout: MaybeString,
pub discardtimeout: MaybeString,
pub discardtimeout: Option<MaybeString>,
pub cachemaxttl: MaybeString,
pub cachemaxnegativettl: MaybeString,
pub cacheminttl: MaybeString,
@ -1141,6 +1161,7 @@ pub struct Advanced {
pub struct Acls {
#[yaserde(rename = "default_action")]
pub default_action: String,
pub acl: Option<RawXml>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -1154,12 +1175,12 @@ pub struct Dnsbl {
pub blocklists: MaybeString,
pub wildcards: MaybeString,
pub address: MaybeString,
pub nxdomain: i32,
pub nxdomain: Option<i32>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Forwarding {
pub enabled: i32,
pub enabled: Option<i32>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -1193,7 +1214,7 @@ impl Host {
server,
mxprio: MaybeString::default(),
mx: MaybeString::default(),
description: None
description: None,
}
}
}
@ -1349,553 +1370,6 @@ pub struct ConfigOpenVPN {
pub StaticKeys: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
#[yaserde(rename = "HAProxy")]
pub struct HAProxy {
#[yaserde(attribute)]
pub version: String,
pub general: HaProxyGeneral,
pub frontends: HAProxyFrontends,
pub backends: HAProxyBackends,
pub servers: HAProxyServers,
pub healthchecks: HAProxyHealthChecks,
pub acls: MaybeString,
pub actions: MaybeString,
pub luas: MaybeString,
pub fcgis: MaybeString,
pub errorfiles: MaybeString,
pub mapfiles: MaybeString,
pub groups: MaybeString,
pub users: MaybeString,
pub cpus: MaybeString,
pub resolvers: MaybeString,
pub mailers: MaybeString,
pub maintenance: Maintenance,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Maintenance {
#[yaserde(rename = "cronjobs")]
pub cronjobs: CronJobs,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct CronJobs {
#[yaserde(rename = "syncCerts")]
pub sync_certs: u32,
#[yaserde(rename = "syncCertsCron")]
pub sync_certs_cron: MaybeString,
#[yaserde(rename = "updateOcsp")]
pub update_ocsp: u32,
#[yaserde(rename = "updateOcspCron")]
pub update_ocsp_cron: MaybeString,
#[yaserde(rename = "reloadService")]
pub reload_service: u32,
#[yaserde(rename = "reloadServiceCron")]
pub reload_service_cron: MaybeString,
#[yaserde(rename = "restartService")]
pub restart_service: u32,
#[yaserde(rename = "restartServiceCron")]
pub restart_service_cron: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyGeneral {
pub enabled: i32,
#[yaserde(rename = "gracefulStop")]
pub graceful_stop: i32,
#[yaserde(rename = "hardStopAfter")]
pub hard_stop_after: String,
#[yaserde(rename = "closeSpreadTime")]
pub close_spread_time: MaybeString,
#[yaserde(rename = "seamlessReload")]
pub seamless_reload: i32,
#[yaserde(rename = "storeOcsp")]
pub store_ocsp: i32,
#[yaserde(rename = "showIntro")]
pub show_intro: i32,
pub peers: Peers,
pub tuning: Tuning,
pub defaults: HaProxyDefaults,
pub logging: HaProxyLogging,
pub stats: Stats,
pub cache: HaProxyCache,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Peers {
pub enabled: i32,
pub name1: MaybeString,
pub listen1: MaybeString,
pub port1: i32,
pub name2: MaybeString,
pub listen2: MaybeString,
pub port2: i32,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Tuning {
pub root: i32,
#[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")]
pub max_dh_size: i32,
#[yaserde(rename = "bufferSize")]
pub buffer_size: i32,
#[yaserde(rename = "spreadChecks")]
pub spread_checks: i32,
#[yaserde(rename = "bogusProxyEnabled")]
pub bogus_proxy_enabled: i32,
#[yaserde(rename = "luaMaxMem")]
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")]
pub ssl_bind_options: String,
#[yaserde(rename = "ssl_minVersion")]
pub ssl_min_version: String,
#[yaserde(rename = "ssl_maxVersion")]
pub ssl_max_version: MaybeString,
#[yaserde(rename = "ssl_cipherList")]
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)]
pub struct HaProxyDefaults {
#[yaserde(rename = "maxConnections")]
pub max_connections: MaybeString,
#[yaserde(rename = "maxConnectionsServers")]
pub max_connections_servers: MaybeString,
#[yaserde(rename = "timeoutClient")]
pub timeout_client: String,
#[yaserde(rename = "timeoutConnect")]
pub timeout_connect: String,
#[yaserde(rename = "timeoutCheck")]
pub timeout_check: MaybeString,
#[yaserde(rename = "timeoutServer")]
pub timeout_server: String,
pub retries: i32,
pub redispatch: String,
pub init_addr: String,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyLogging {
pub host: String,
pub facility: String,
pub level: String,
pub length: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Stats {
pub enabled: i32,
pub port: i32,
#[yaserde(rename = "remoteEnabled")]
pub remote_enabled: i32,
#[yaserde(rename = "remoteBind")]
pub remote_bind: MaybeString,
#[yaserde(rename = "authEnabled")]
pub auth_enabled: i32,
#[yaserde(rename = "users")]
pub users: MaybeString,
#[yaserde(rename = "allowedUsers")]
pub allowed_users: MaybeString,
#[yaserde(rename = "allowedGroups")]
pub allowed_groups: MaybeString,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
pub prometheus_enabled: i32,
pub prometheus_bind: String,
pub prometheus_path: String,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HaProxyCache {
pub enabled: i32,
#[yaserde(rename = "totalMaxSize")]
pub total_max_size: i32,
#[yaserde(rename = "maxAge")]
pub max_age: i32,
#[yaserde(rename = "maxObjectSize")]
pub max_object_size: MaybeString,
#[yaserde(rename = "processVary")]
pub process_vary: i32,
#[yaserde(rename = "maxSecondaryEntries")]
pub max_secondary_entries: i32,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyFrontends {
pub frontend: Vec<Frontend>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Frontend {
#[yaserde(attribute)]
pub uuid: String,
pub id: String,
pub enabled: i32,
pub name: String,
pub description: String,
pub bind: String,
#[yaserde(rename = "bindOptions")]
pub bind_options: MaybeString,
pub mode: String,
#[yaserde(rename = "defaultBackend")]
pub default_backend: String,
pub ssl_enabled: i32,
pub ssl_certificates: MaybeString,
pub ssl_default_certificate: MaybeString,
#[yaserde(rename = "ssl_customOptions")]
pub ssl_custom_options: MaybeString,
#[yaserde(rename = "ssl_advancedEnabled")]
pub ssl_advanced_enabled: i32,
#[yaserde(rename = "ssl_bindOptions")]
pub ssl_bind_options: String,
#[yaserde(rename = "ssl_minVersion")]
pub ssl_min_version: String,
#[yaserde(rename = "ssl_maxVersion")]
pub ssl_max_version: MaybeString,
#[yaserde(rename = "ssl_cipherList")]
pub ssl_cipher_list: String,
#[yaserde(rename = "ssl_cipherSuites")]
pub ssl_cipher_suites: String,
#[yaserde(rename = "ssl_hstsEnabled")]
pub ssl_hsts_enabled: i32,
#[yaserde(rename = "ssl_hstsIncludeSubDomains")]
pub ssl_hsts_include_sub_domains: i32,
#[yaserde(rename = "ssl_hstsPreload")]
pub ssl_hsts_preload: i32,
#[yaserde(rename = "ssl_hstsMaxAge")]
pub ssl_hsts_max_age: i32,
#[yaserde(rename = "ssl_clientAuthEnabled")]
pub ssl_client_auth_enabled: i32,
#[yaserde(rename = "ssl_clientAuthVerify")]
pub ssl_client_auth_verify: String,
#[yaserde(rename = "ssl_clientAuthCAs")]
pub ssl_client_auth_cas: MaybeString,
#[yaserde(rename = "ssl_clientAuthCRLs")]
pub ssl_client_auth_cr_ls: MaybeString,
#[yaserde(rename = "basicAuthEnabled")]
pub basic_auth_enabled: i32,
#[yaserde(rename = "basicAuthUsers")]
pub basic_auth_users: MaybeString,
#[yaserde(rename = "basicAuthGroups")]
pub basic_auth_groups: MaybeString,
#[yaserde(rename = "tuning_maxConnections")]
pub tuning_max_connections: MaybeString,
#[yaserde(rename = "tuning_timeoutClient")]
pub tuning_timeout_client: MaybeString,
#[yaserde(rename = "tuning_timeoutHttpReq")]
pub tuning_timeout_http_req: MaybeString,
#[yaserde(rename = "tuning_timeoutHttpKeepAlive")]
pub tuning_timeout_http_keep_alive: MaybeString,
#[yaserde(rename = "linkedCpuAffinityRules")]
pub linked_cpu_affinity_rules: MaybeString,
pub tuning_shards: MaybeString,
#[yaserde(rename = "logging_dontLogNull")]
pub logging_dont_log_null: i32,
#[yaserde(rename = "logging_dontLogNormal")]
pub logging_dont_log_normal: i32,
#[yaserde(rename = "logging_logSeparateErrors")]
pub logging_log_separate_errors: i32,
#[yaserde(rename = "logging_detailedLog")]
pub logging_detailed_log: i32,
#[yaserde(rename = "logging_socketStats")]
pub logging_socket_stats: i32,
pub stickiness_pattern: MaybeString,
#[yaserde(rename = "stickiness_dataTypes")]
pub stickiness_data_types: MaybeString,
pub stickiness_expire: String,
pub stickiness_size: String,
pub stickiness_counter: i32,
pub stickiness_counter_key: String,
pub stickiness_length: MaybeString,
#[yaserde(rename = "stickiness_connRatePeriod")]
pub stickiness_conn_rate_period: String,
#[yaserde(rename = "stickiness_sessRatePeriod")]
pub stickiness_sess_rate_period: String,
#[yaserde(rename = "stickiness_httpReqRatePeriod")]
pub stickiness_http_req_rate_period: String,
#[yaserde(rename = "stickiness_httpErrRatePeriod")]
pub stickiness_http_err_rate_period: String,
#[yaserde(rename = "stickiness_bytesInRatePeriod")]
pub stickiness_bytes_in_rate_period: String,
#[yaserde(rename = "stickiness_bytesOutRatePeriod")]
pub stickiness_bytes_out_rate_period: String,
#[yaserde(rename = "http2Enabled")]
pub http2_enabled: i32,
#[yaserde(rename = "http2Enabled_nontls")]
pub http2_enabled_nontls: i32,
pub advertised_protocols: String,
#[yaserde(rename = "forwardFor")]
pub forward_for: i32,
pub prometheus_enabled: i32,
pub prometheus_path: String,
#[yaserde(rename = "connectionBehaviour")]
pub connection_behaviour: String,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
#[yaserde(rename = "linkedActions")]
pub linked_actions: MaybeString,
#[yaserde(rename = "linkedErrorfiles")]
pub linked_error_files: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyBackends {
#[yaserde(rename = "backend")]
pub backends: Vec<Backend>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct Backend {
#[yaserde(attribute, rename = "uuid")]
pub uuid: String,
#[yaserde(rename = "id")]
pub id: String,
#[yaserde(rename = "enabled")]
pub enabled: u8,
#[yaserde(rename = "name")]
pub name: String,
#[yaserde(rename = "description")]
pub description: MaybeString,
#[yaserde(rename = "mode")]
pub mode: String,
#[yaserde(rename = "algorithm")]
pub algorithm: String,
#[yaserde(rename = "random_draws")]
pub random_draws: Option<u32>,
#[yaserde(rename = "proxyProtocol")]
pub proxy_protocol: MaybeString,
#[yaserde(rename = "linkedServers")]
pub linked_servers: MaybeString,
#[yaserde(rename = "linkedFcgi")]
pub linked_fcgi: MaybeString,
#[yaserde(rename = "linkedResolver")]
pub linked_resolver: MaybeString,
#[yaserde(rename = "resolverOpts")]
pub resolver_opts: MaybeString,
#[yaserde(rename = "resolvePrefer")]
pub resolve_prefer: MaybeString,
#[yaserde(rename = "source")]
pub source: MaybeString,
#[yaserde(rename = "healthCheckEnabled")]
pub health_check_enabled: u8,
#[yaserde(rename = "healthCheck")]
pub health_check: MaybeString,
#[yaserde(rename = "healthCheckLogStatus")]
pub health_check_log_status: u8,
#[yaserde(rename = "checkInterval")]
pub check_interval: MaybeString,
#[yaserde(rename = "checkDownInterval")]
pub check_down_interval: MaybeString,
#[yaserde(rename = "healthCheckFall")]
pub health_check_fall: MaybeString,
#[yaserde(rename = "healthCheckRise")]
pub health_check_rise: MaybeString,
#[yaserde(rename = "linkedMailer")]
pub linked_mailer: MaybeString,
#[yaserde(rename = "http2Enabled")]
pub http2_enabled: u8,
#[yaserde(rename = "http2Enabled_nontls")]
pub http2_enabled_nontls: u8,
#[yaserde(rename = "ba_advertised_protocols")]
pub ba_advertised_protocols: String,
#[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,
pub persistence_cookiename: MaybeString,
pub persistence_stripquotes: u8,
pub stickiness_pattern: MaybeString,
#[yaserde(rename = "stickiness_dataTypes")]
pub stickiness_data_types: MaybeString,
pub stickiness_expire: String,
pub stickiness_size: String,
pub stickiness_cookiename: MaybeString,
pub stickiness_cookielength: MaybeString,
#[yaserde(rename = "stickiness_connRatePeriod")]
pub stickiness_conn_rate_period: String,
#[yaserde(rename = "stickiness_sessRatePeriod")]
pub stickiness_sess_rate_period: String,
#[yaserde(rename = "stickiness_httpReqRatePeriod")]
pub stickiness_http_req_rate_period: String,
#[yaserde(rename = "stickiness_httpErrRatePeriod")]
pub stickiness_http_err_rate_period: String,
#[yaserde(rename = "stickiness_bytesInRatePeriod")]
pub stickiness_bytes_in_rate_period: String,
#[yaserde(rename = "stickiness_bytesOutRatePeriod")]
pub stickiness_bytes_out_rate_period: String,
#[yaserde(rename = "basicAuthEnabled")]
pub basic_auth_enabled: u8,
#[yaserde(rename = "basicAuthUsers")]
pub basic_auth_users: MaybeString,
#[yaserde(rename = "basicAuthGroups")]
pub basic_auth_groups: MaybeString,
#[yaserde(rename = "tuning_timeoutConnect")]
pub tuning_timeout_connect: MaybeString,
#[yaserde(rename = "tuning_timeoutCheck")]
pub tuning_timeout_check: MaybeString,
#[yaserde(rename = "tuning_timeoutServer")]
pub tuning_timeout_server: MaybeString,
#[yaserde(rename = "tuning_retries")]
pub tuning_retries: MaybeString,
#[yaserde(rename = "customOptions")]
pub custom_options: MaybeString,
#[yaserde(rename = "tuning_defaultserver")]
pub tuning_defaultserver: MaybeString,
#[yaserde(rename = "tuning_noport")]
pub tuning_noport: u8,
#[yaserde(rename = "tuning_httpreuse")]
pub tuning_httpreuse: MaybeString,
#[yaserde(rename = "tuning_caching")]
pub tuning_caching: u8,
#[yaserde(rename = "linkedActions")]
pub linked_actions: MaybeString,
#[yaserde(rename = "linkedErrorfiles")]
pub linked_errorfiles: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyServers {
#[yaserde(rename = "server")]
pub servers: Vec<HAProxyServer>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyServer {
#[yaserde(attribute, rename = "uuid")]
pub uuid: String,
pub id: String,
pub enabled: u8,
pub name: String,
pub description: MaybeString,
pub address: String,
pub port: u16,
pub checkport: MaybeString,
pub mode: String,
pub multiplexer_protocol: String,
#[yaserde(rename = "type")]
pub server_type: String,
#[yaserde(rename = "serviceName")]
pub service_name: MaybeString,
pub number: MaybeString,
#[yaserde(rename = "linkedResolver")]
pub linked_resolver: MaybeString,
#[yaserde(rename = "resolverOpts")]
pub resolver_opts: MaybeString,
#[yaserde(rename = "resolvePrefer")]
pub resolve_prefer: MaybeString,
pub ssl: u8,
#[yaserde(rename = "sslSNI")]
pub ssl_sni: MaybeString,
#[yaserde(rename = "sslVerify")]
pub ssl_verify: u8,
#[yaserde(rename = "sslCA")]
pub ssl_ca: MaybeString,
#[yaserde(rename = "sslCRL")]
pub ssl_crl: MaybeString,
#[yaserde(rename = "sslClientCertificate")]
pub ssl_client_certificate: MaybeString,
#[yaserde(rename = "maxConnections")]
pub max_connections: MaybeString,
pub weight: u32,
#[yaserde(rename = "checkInterval")]
pub check_interval: MaybeString,
#[yaserde(rename = "checkDownInterval")]
pub check_down_interval: MaybeString,
pub source: MaybeString,
pub advanced: MaybeString,
#[yaserde(rename = "unix_socket")]
pub unix_socket: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyHealthChecks {
#[yaserde(rename = "healthcheck")]
pub healthchecks: Vec<HAProxyHealthCheck>,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct HAProxyHealthCheck {
#[yaserde(attribute)]
pub uuid: String,
pub name: String,
pub description: MaybeString,
#[yaserde(rename = "type")]
pub health_check_type: String,
pub interval: String,
pub ssl: String,
#[yaserde(rename = "sslSNI")]
pub ssl_sni: MaybeString,
pub force_ssl: u8,
pub checkport: MaybeString,
pub http_method: String,
pub http_uri: String,
pub http_version: MaybeString,
#[yaserde(rename = "http_host")]
pub http_host: MaybeString,
#[yaserde(rename = "http_expressionEnabled")]
pub http_expression_enabled: Option<u8>,
pub http_expression: MaybeString,
pub http_negate: MaybeString,
pub http_value: MaybeString,
pub tcp_enabled: MaybeString,
#[yaserde(rename = "tcp_sendValue")]
pub tcp_send_value: MaybeString,
#[yaserde(rename = "tcp_matchType")]
pub tcp_match_type: MaybeString,
pub tcp_negate: MaybeString,
#[yaserde(rename = "tcp_matchValue")]
pub tcp_match_value: MaybeString,
pub agent_port: MaybeString,
pub mysql_user: MaybeString,
pub mysql_post41: MaybeString,
pub pgsql_user: MaybeString,
pub smtp_domain: MaybeString,
pub esmtp_domain: MaybeString,
#[yaserde(rename = "agentPort")]
pub agent_port_uppercase: MaybeString,
#[yaserde(rename = "dbUser")]
pub db_user: MaybeString,
#[yaserde(rename = "smtpDomain")]
pub smtp_domain_uppercase: MaybeString,
}
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
pub struct StaticRoutes {

View File

@ -1,7 +1,11 @@
use std::{net::Ipv4Addr, sync::Arc, time::Duration};
use std::{sync::Arc, time::Duration};
use crate::{config::{SshConfigManager, SshCredentials, SshOPNSenseShell}, error::Error, modules::{dhcp::DhcpConfig, dns::DnsConfig}};
use log::trace;
use crate::{
config::{SshConfigManager, SshCredentials, SshOPNSenseShell},
error::Error,
modules::{dhcp::DhcpConfig, dns::DnsConfig, load_balancer::LoadBalancerConfig},
};
use log::{info, trace};
use opnsense_config_xml::OPNsense;
use russh::client;
@ -19,13 +23,8 @@ impl Config {
repository: Arc<dyn ConfigManager>,
shell: Arc<dyn OPNsenseShell>,
) -> Result<Self, Error> {
let xml = repository.load_as_str().await?;
trace!("xml {}", xml);
let opnsense = OPNsense::from(xml);
Ok(Self {
opnsense,
opnsense: Self::get_opnsense_instance(repository.clone()).await?,
repository,
shell,
})
@ -39,6 +38,26 @@ impl Config {
DnsConfig::new(&mut self.opnsense, self.shell.clone())
}
pub fn load_balancer(&mut self) -> LoadBalancerConfig {
LoadBalancerConfig::new(&mut self.opnsense, self.shell.clone())
}
pub async fn install_package(&mut self, package_name: &str) -> Result<(), Error> {
info!("Installing opnsense package {package_name}");
let output = self.shell
.exec(&format!("pkg install -y {package_name}"))
.await?;
info!("Installation output {output}");
self.reload_config().await?;
Ok(())
}
async fn reload_config(&mut self) -> Result<(), Error> {
info!("Reloading opnsense live config");
self.opnsense = Self::get_opnsense_instance(self.repository.clone()).await?;
Ok(())
}
pub async fn restart_dns(&self) -> Result<(), Error> {
self.shell.exec("configctl unbound restart").await?;
Ok(())
@ -47,9 +66,7 @@ impl Config {
/// 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
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
@ -60,7 +77,12 @@ impl Config {
.await
}
pub async fn from_credentials(ipaddr: std::net::IpAddr, username: &str, password: &str) -> Self {
pub async fn from_credentials(
ipaddr: std::net::IpAddr,
port: Option<u16>,
username: &str,
password: &str,
) -> Self {
let config = Arc::new(client::Config {
inactivity_timeout: Some(Duration::from_secs(5)),
..<_>::default()
@ -71,15 +93,20 @@ impl Config {
password: String::from(password),
};
let shell = Arc::new(SshOPNSenseShell::new(
(ipaddr, 22),
credentials,
config,
));
let port = port.unwrap_or(22);
let shell = Arc::new(SshOPNSenseShell::new((ipaddr, port), credentials, config));
let manager = Arc::new(SshConfigManager::new(shell.clone()));
Config::new(manager, shell).await.unwrap()
}
async fn get_opnsense_instance(repository: Arc<dyn ConfigManager>) -> Result<OPNsense, Error> {
let xml = repository.load_as_str().await?;
trace!("xml {}", xml);
Ok(OPNsense::from(xml))
}
}
#[cfg(test)]

View File

@ -5,7 +5,7 @@ use std::{
};
use async_trait::async_trait;
use log::debug;
use log::{debug, info};
use russh::{
client::{Config, Handler, Msg},
Channel,
@ -28,6 +28,7 @@ pub struct SshOPNSenseShell {
#[async_trait]
impl OPNsenseShell for SshOPNSenseShell {
async fn exec(&self, command: &str) -> Result<String, Error> {
info!("Executing command on SshOPNSenseShell {command}");
self.run_command(command).await
}

View File

@ -33,7 +33,12 @@ mod test {
}
async fn initialize_config() -> Config {
Config::from_credentials(Ipv4Addr::new(192, 168, 5, 229), "root", "opnsense").await
Config::from_credentials(
std::net::IpAddr::V4(Ipv4Addr::new(192, 168, 5, 229)),
"root",
"opnsense",
)
.await
}
async fn get_static_mappings() -> Vec<StaticMap> {

View File

@ -202,6 +202,11 @@ impl<'a> DhcpConfig<'a> {
self.enable_netboot();
self.get_lan_dhcpd().nextserver = Some(ip.to_string());
}
pub fn set_boot_filename(&mut self, boot_filename: &str) {
self.enable_netboot();
self.get_lan_dhcpd().filename64 = Some(boot_filename.to_string());
}
}
#[cfg(test)]

View File

@ -39,7 +39,7 @@ impl<'a> DnsConfig<'a> {
None => todo!("Handle case where unboundplus is not used"),
};
unbound.general.regdhcp = register as i8;
unbound.general.regdhcpstatic = register as i8;
unbound.general.regdhcp = Some(register as i8);
unbound.general.regdhcpstatic = Some(register as i8);
}
}

View File

@ -0,0 +1,59 @@
use std::sync::Arc;
use log::warn;
use opnsense_config_xml::{
Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer, OPNsense,
};
use crate::config::OPNsenseShell;
pub struct LoadBalancerConfig<'a> {
opnsense: &'a mut OPNsense,
opnsense_shell: Arc<dyn OPNsenseShell>,
}
impl<'a> LoadBalancerConfig<'a> {
pub fn new(opnsense: &'a mut OPNsense, opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
Self {
opnsense,
opnsense_shell,
}
}
pub fn get_full_config(&self) -> &Option<HAProxy> {
&self.opnsense.opnsense.haproxy
}
fn with_haproxy<F, R>(&mut self, f: F) -> R
where
F: FnOnce(&mut HAProxy) -> R,
{
match &mut self.opnsense.opnsense.haproxy.as_mut() {
Some(haproxy) => f(haproxy),
None => unimplemented!(
"Adding a backend is not supported when haproxy config does not exist yet"
),
}
}
pub fn enable(&mut self, enabled: bool) {
self.with_haproxy(|haproxy| haproxy.general.enabled = enabled as i32);
}
pub fn add_backend(&mut self, backend: HAProxyBackend) {
warn!("TODO make sure this new backend does not refer non-existing entities like servers or health checks");
self.with_haproxy(|haproxy| haproxy.backends.backends.push(backend));
}
pub fn add_frontend(&mut self, frontend: Frontend) {
self.with_haproxy(|haproxy| haproxy.frontends.frontend.push(frontend));
}
pub fn add_healthcheck(&mut self, healthcheck: HAProxyHealthCheck) {
self.with_haproxy(|haproxy| haproxy.healthchecks.healthchecks.push(healthcheck));
}
pub fn add_servers(&mut self, mut servers: Vec<HAProxyServer>) {
self.with_haproxy(|haproxy| haproxy.servers.servers.append(&mut servers));
}
}

View File

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