fix(load-balancer): implement missing HAProxy reload and sanitize output handling

Implement the `reload_restart` method in `LoadBalancerConfig` to ensure proper HAProxy configuration management. Additionally, enhance SSH command execution by sanitizing and logging outputs effectively. This ensures robust handling of HAProxy configurations and improves debugging capabilities through trace-level logs.
This commit is contained in:
jeangab 2025-01-08 16:30:56 -05:00
parent a55c63ffa6
commit 0af8e7e6a8
7 changed files with 56 additions and 23 deletions

View File

@ -15,6 +15,7 @@ pub trait LoadBalancer: Send + Sync {
async fn list_services(&self) -> Vec<LoadBalancerService>; async fn list_services(&self) -> Vec<LoadBalancerService>;
async fn ensure_initialized(&self) -> Result<(), ExecutorError>; async fn ensure_initialized(&self) -> Result<(), ExecutorError>;
async fn commit_config(&self) -> Result<(), ExecutorError>; async fn commit_config(&self) -> Result<(), ExecutorError>;
async fn reload_restart(&self) -> Result<(), ExecutorError>;
async fn ensure_service_exists( async fn ensure_service_exists(
&self, &self,
service: &LoadBalancerService, service: &LoadBalancerService,

View File

@ -120,8 +120,6 @@ pub enum Action {
#[derive(Clone, Debug, PartialEq, Eq, Hash)] #[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct MacAddress(pub [u8; 6]); pub struct MacAddress(pub [u8; 6]);
// TODO create a small macro to provide a nice API to initiate a MacAddress
// MacAddress::from!("00:90:7f:df:2c:23"),
impl MacAddress { impl MacAddress {
#[cfg(test)] #[cfg(test)]
@ -133,7 +131,7 @@ impl MacAddress {
impl From<&MacAddress> for String { impl From<&MacAddress> for String {
fn from(value: &MacAddress) -> Self { fn from(value: &MacAddress) -> Self {
format!( format!(
"{}:{}:{}:{}:{}:{}", "{:02x}:{:02x}:{:02x}:{:02x}:{:02x}:{:02x}",
value.0[0], value.0[1], value.0[2], value.0[3], value.0[4], value.0[5] value.0[0], value.0[1], value.0[2], value.0[3], value.0[4], value.0[5]
) )
} }
@ -142,8 +140,8 @@ impl From<&MacAddress> for String {
impl std::fmt::Display for MacAddress { impl std::fmt::Display for MacAddress {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!( f.write_fmt(format_args!(
"MacAddress {}:{}:{}:{}:{}:{}", "MacAddress {}",
self.0[0], self.0[1], self.0[2], self.0[3], self.0[4], self.0[5] String::from(self)
)) ))
} }
} }

View File

@ -3,9 +3,13 @@ use log::{debug, info, warn};
use opnsense_config_xml::{Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer}; use opnsense_config_xml::{Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer};
use uuid::Uuid; use uuid::Uuid;
use crate::{executors::ExecutorError, topology::{ use crate::{
BackendServer, HealthCheck, HttpMethod, HttpStatusCode, IpAddress, LoadBalancer, LoadBalancerService, LogicalHost executors::ExecutorError,
}}; topology::{
BackendServer, HealthCheck, HttpMethod, HttpStatusCode, IpAddress, LoadBalancer,
LoadBalancerService, LogicalHost,
},
};
use super::OPNSenseFirewall; use super::OPNSenseFirewall;
@ -38,23 +42,33 @@ impl LoadBalancer for OPNSenseFirewall {
} }
async fn commit_config(&self) -> Result<(), ExecutorError> { async fn commit_config(&self) -> Result<(), ExecutorError> {
OPNSenseFirewall::commit_config(self).await?; OPNSenseFirewall::commit_config(self).await
todo!("Make sure load balancer is reloaded properly") }
async fn reload_restart(&self) -> Result<(), ExecutorError> {
self.opnsense_config
.write()
.await
.load_balancer()
.reload_restart()
.await
.map_err(|e| ExecutorError::UnexpectedError(e.to_string()))
} }
async fn ensure_initialized(&self) -> Result<(), ExecutorError> { async fn ensure_initialized(&self) -> Result<(), ExecutorError> {
let mut config = self.opnsense_config.write().await; let mut config = self.opnsense_config.write().await;
let load_balancer = config.load_balancer(); let load_balancer = config.load_balancer();
if let Some(_) = load_balancer.get_full_config() { if let Some(config) = load_balancer.get_full_config() {
debug!("HAProxy config available in opnsense config, assuming it is already installed"); debug!("HAProxy config available in opnsense config, assuming it is already installed, {config:?}");
return Ok(()); } else {
config.install_package("os-haproxy").await.map_err(|e| {
ExecutorError::UnexpectedError(format!(
"Executor failed when trying to install os-haproxy package with error {e:?}"
))
})?;
todo!()
} }
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); config.load_balancer().enable(true);
Ok(()) Ok(())
} }
@ -67,7 +81,6 @@ impl LoadBalancer for OPNSenseFirewall {
} }
} }
pub(crate) fn haproxy_xml_config_to_harmony_loadbalancer( pub(crate) fn haproxy_xml_config_to_harmony_loadbalancer(
haproxy: &Option<HAProxy>, haproxy: &Option<HAProxy>,
) -> Vec<LoadBalancerService> { ) -> Vec<LoadBalancerService> {

View File

@ -64,7 +64,12 @@ impl Interpret for LoadBalancerInterpret {
info!("Applying load balancer configuration"); info!("Applying load balancer configuration");
topology.load_balancer.commit_config().await?; topology.load_balancer.commit_config().await?;
todo!() info!("Making a full reload and restart of haproxy");
topology.load_balancer.reload_restart().await?;
Ok(Outcome::success(format!(
"Load balancer successfully configured {} services",
self.score.public_services.len() + self.score.private_services.len()
)))
} }
fn get_name(&self) -> InterpretName { fn get_name(&self) -> InterpretName {
InterpretName::LoadBalancer InterpretName::LoadBalancer

View File

@ -110,6 +110,7 @@ pub struct Filters {
#[yaserde(rename = "rule")] #[yaserde(rename = "rule")]
pub rules: Vec<Rule>, pub rules: Vec<Rule>,
pub bypassstaticroutes: Option<MaybeString>, pub bypassstaticroutes: Option<MaybeString>,
pub scrub: Option<RawXml>,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]
@ -338,6 +339,10 @@ pub struct Snmpd {
pub struct Syslog { pub struct Syslog {
pub reverse: Option<MaybeString>, pub reverse: Option<MaybeString>,
pub preservelogs: Option<MaybeString>, pub preservelogs: Option<MaybeString>,
pub nologdefaultblock: Option<u8>,
pub nologdefaultpass: Option<u8>,
pub nologbogons: Option<u8>,
pub nologprivatenets: Option<u8>,
} }
#[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)] #[derive(Default, PartialEq, Debug, YaSerialize, YaDeserialize)]

View File

@ -6,7 +6,7 @@ use std::{
use tokio_stream::StreamExt; use tokio_stream::StreamExt;
use async_trait::async_trait; use async_trait::async_trait;
use log::{debug, info}; use log::{debug, info, trace};
use russh::{ use russh::{
client::{Config, Handler, Msg}, client::{Config, Handler, Msg},
Channel, Channel,
@ -205,5 +205,7 @@ async fn wait_for_completion(channel: &mut Channel<Msg>) -> Result<String, Error
} }
} }
Ok(String::from_utf8(output).unwrap_or_default()) let output = String::from_utf8(output).expect("Output should be UTF-8 compatible");
trace!("{output}");
Ok(output)
} }

View File

@ -5,7 +5,7 @@ use opnsense_config_xml::{
Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer, OPNsense, Frontend, HAProxy, HAProxyBackend, HAProxyHealthCheck, HAProxyServer, OPNsense,
}; };
use crate::config::OPNsenseShell; use crate::{config::OPNsenseShell, Error};
pub struct LoadBalancerConfig<'a> { pub struct LoadBalancerConfig<'a> {
opnsense: &'a mut OPNsense, opnsense: &'a mut OPNsense,
@ -56,4 +56,13 @@ impl<'a> LoadBalancerConfig<'a> {
pub fn add_servers(&mut self, mut servers: Vec<HAProxyServer>) { pub fn add_servers(&mut self, mut servers: Vec<HAProxyServer>) {
self.with_haproxy(|haproxy| haproxy.servers.servers.append(&mut servers)); self.with_haproxy(|haproxy| haproxy.servers.servers.append(&mut servers));
} }
pub async fn reload_restart(&self) -> Result<(), Error> {
self.opnsense_shell.exec("configctl haproxy stop").await?;
self.opnsense_shell.exec("configctl template reload OPNsense/HAProxy").await?;
self.opnsense_shell.exec("configctl template reload OPNsense/Syslog").await?;
self.opnsense_shell.exec("configctl haproxy configtest").await?;
self.opnsense_shell.exec("configctl haproxy start").await?;
Ok(())
}
} }