forked from NationTech/harmony
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:
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
59
harmony-rs/opnsense-config/src/modules/load_balancer.rs
Normal file
59
harmony-rs/opnsense-config/src/modules/load_balancer.rs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod dhcp;
|
||||
pub mod dns;
|
||||
pub mod load_balancer;
|
||||
|
||||
Reference in New Issue
Block a user