feat: Add verification of opnsense package installation, fix opnsense-config tests, add log file to tui

This commit is contained in:
2025-02-02 17:06:23 -05:00
parent 3eac78c6d3
commit 1877570d7c
10 changed files with 174 additions and 84 deletions

View File

@@ -8,7 +8,7 @@ use crate::{
tftp::TftpConfig,
},
};
use log::{info, trace};
use log::{debug, info, trace, warn};
use opnsense_config_xml::OPNsense;
use russh::client;
@@ -57,6 +57,30 @@ impl Config {
self.shell.upload_folder(source, destination).await
}
/// Checks in config file if system.firmware.plugins csv field contains the specified package
/// name.
///
/// Given this
/// ```xml
/// <opnsense>
/// <system>
/// <firmware>
/// <plugins>os-haproxy,os-iperf,os-cpu-microcode-intel</plugins>
/// </firmware>
/// </system>
/// </opnsense>
/// ```
///
/// is_package_installed("os-cpu"); // false
/// is_package_installed("os-haproxy"); // true
/// is_package_installed("os-cpu-microcode-intel"); // true
pub fn is_package_installed(&self, package_name: &str) -> bool {
match &self.opnsense.system.firmware.plugins.content {
Some(plugins) => is_package_in_csv(plugins, package_name),
None => false,
}
}
// Here maybe we should take ownership of `mut self` instead of `&mut self`
// I don't think there can be faulty pointers to previous versions of the config but I have a
// hard time wrapping my head around it right now :
@@ -70,11 +94,35 @@ impl Config {
// read-only reference across the &mut call
pub async fn install_package(&mut self, package_name: &str) -> Result<(), Error> {
info!("Installing opnsense package {package_name}");
self.check_pkg_opnsense_org_connection().await?;
let output = self.shell
.exec(&format!("/bin/sh -c \"export LOCKFILE=/dev/stdout && /usr/local/opnsense/scripts/firmware/install.sh {package_name}\""))
.await?;
info!("Installation output {output}");
self.reload_config().await?;
let is_installed = self.is_package_installed(package_name);
debug!("Verifying package installed successfully {is_installed}");
if !is_installed {
info!("Installation successful for {package_name}");
Ok(())
} else {
let msg = format!("Package installation failed for {package_name}, see above logs");
warn!("{}", msg);
Err(Error::Unexpected(msg))
}
}
pub async fn check_pkg_opnsense_org_connection(&mut self) -> Result<(), Error> {
let pkg_url = "https://pkg.opnsense.org";
info!("Verifying connection to {pkg_url}");
let output = self
.shell
.exec(&format!("/bin/sh -c \"curl -v {pkg_url}\""))
.await?;
info!("{}", output);
Ok(())
}
@@ -150,8 +198,8 @@ mod tests {
async fn test_load_config_from_local_file() {
for path in vec![
"src/tests/data/config-vm-test.xml",
"src/tests/data/config-full-1.xml",
"src/tests/data/config-structure.xml",
"src/tests/data/config-full-1.xml",
] {
let mut test_file_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
test_file_path.push(path);
@@ -217,3 +265,53 @@ mod tests {
assert_eq!(expected_config_file_str, serialized);
}
}
/// Checks if a given package name exists in a comma-separated list of packages.
///
/// # Arguments
///
/// * `csv_string` - A string containing comma-separated package names.
/// * `package_name` - The package name to search for.
///
/// # Returns
///
/// * `true` if the package name is found in the CSV string, `false` otherwise.
fn is_package_in_csv(csv_string: &str, package_name: &str) -> bool {
package_name.len() > 0 && csv_string.split(',').any(|pkg| pkg.trim() == package_name)
}
#[cfg(test)]
mod tests_2 {
use super::*;
#[test]
fn test_is_package_in_csv() {
let csv_string = "os-haproxy,os-iperf,os-cpu-microcode-intel";
assert!(is_package_in_csv(csv_string, "os-haproxy"));
assert!(is_package_in_csv(csv_string, "os-iperf"));
assert!(is_package_in_csv(csv_string, "os-cpu-microcode-intel"));
assert!(!is_package_in_csv(csv_string, "os-cpu"));
assert!(!is_package_in_csv(csv_string, "non-existent-package"));
}
#[test]
fn test_is_package_in_csv_empty() {
let csv_string = "";
assert!(!is_package_in_csv(csv_string, "os-haproxy"));
assert!(!is_package_in_csv(csv_string, ""));
}
#[test]
fn test_is_package_in_csv_whitespace() {
let csv_string = " os-haproxy , os-iperf , os-cpu-microcode-intel ";
assert!(is_package_in_csv(csv_string, "os-haproxy"));
assert!(is_package_in_csv(csv_string, "os-iperf"));
assert!(is_package_in_csv(csv_string, "os-cpu-microcode-intel"));
assert!(!is_package_in_csv(csv_string, " os-haproxy "));
}
}

View File

@@ -35,6 +35,7 @@ mod test {
async fn initialize_config() -> Config {
Config::from_credentials(
std::net::IpAddr::V4(Ipv4Addr::new(192, 168, 5, 229)),
None,
"root",
"opnsense",
)

View File

@@ -184,37 +184,3 @@ impl<'a> DhcpConfig<'a> {
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
use std::net::Ipv4Addr;
#[test]
fn test_ip_in_range() {
let range = Range {
from: "192.168.1.100".to_string(),
to: "192.168.1.200".to_string(),
};
// Test IP within range
let ip = "192.168.1.150".parse::<Ipv4Addr>().unwrap();
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true);
// Test IP at start of range
let ip = "192.168.1.100".parse::<Ipv4Addr>().unwrap();
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true);
// Test IP at end of range
let ip = "192.168.1.200".parse::<Ipv4Addr>().unwrap();
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), true);
// Test IP before range
let ip = "192.168.1.99".parse::<Ipv4Addr>().unwrap();
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), false);
// Test IP after range
let ip = "192.168.1.201".parse::<Ipv4Addr>().unwrap();
assert_eq!(DhcpConfig::is_ip_in_range(&ip, &range), false);
}
}

View File

@@ -257,6 +257,7 @@
</webgui>
<usevirtualterminal>1</usevirtualterminal>
<disableconsolemenu>1</disableconsolemenu>
<disablenatreflection>yes</disablenatreflection>
<disablevlanhwfilter>1</disablevlanhwfilter>
<disablechecksumoffloading>1</disablechecksumoffloading>
<disablesegmentationoffloading>1</disablesegmentationoffloading>
@@ -291,6 +292,7 @@
<plugins>os-ddclient,os-dyndns,os-haproxy,os-wireguard</plugins>
<type/>
<subscription/>
<reboot/>
</firmware>
<sudo_allow_wheel>1</sudo_allow_wheel>
<sudo_allow_group>admins</sudo_allow_group>
@@ -2113,6 +2115,8 @@
<tunneladdress>172.12.0.1/24</tunneladdress>
<disableroutes>0</disableroutes>
<gateway/>
<endpoint/>
<peer_dns/>
<carp_depend_on/>
<peers>03031aec-2e84-462e-9eab-57762dde667a,98e6ca3d-1de9-449b-be80-77022221b509,67c0ace5-e802-4d2b-a536-f8b7a2db6f99,74b60fff-7844-4097-9966-f1c2b1ad29ff,3de82ad5-bc1b-4b91-9598-f906e58ac937,a95e6b5e-24a4-40b5-bb41-b79e784f6f1c,6c9a12c6-c1ca-4c14-866b-975406a30590,c33b308b-7125-4688-9561-989ace8787b5,e43f004a-23bf-4027-8fb0-953fbb40479f</peers>
</server>
@@ -2282,10 +2286,14 @@
<sslServerVerify>ignore</sslServerVerify>
<maxDHSize>2048</maxDHSize>
<bufferSize>16384</bufferSize>
<resolversPrefer>ipv4</resolversPrefer>
<spreadChecks>2</spreadChecks>
<bogusProxyEnabled>0</bogusProxyEnabled>
<luaMaxMem>0</luaMaxMem>
<customOptions/>
<ocspUpdateEnabled>0</ocspUpdateEnabled>
<ocspUpdateMinDelay>300</ocspUpdateMinDelay>
<ocspUpdateMaxDelay>3600</ocspUpdateMaxDelay>
<ssl_defaultsEnabled>0</ssl_defaultsEnabled>
<ssl_bindOptions>prefer-client-ciphers</ssl_bindOptions>
<ssl_minVersion>TLSv1.2</ssl_minVersion>

View File

@@ -44,16 +44,16 @@
<otp_seed/>
</user>
<user>
<password>$2y$11$55555555556D8198uOASIDJaiojdjd1oijdijosaoijdaoidOIASJDoijdoiadOASdoiK</password>
<scope>user</scope>
<name>someuser</name>
<descr/>
<scope>user</scope>
<password>$2y$11$55555555556D8198uOASIDJaiojdjd1oijdijosaoijdaoidOIASJDoijdoiadOASdoiK</password>
<uid>2000</uid>
<expires/>
<authorizedkeys/>
<ipsecpsk/>
<otp_seed/>
<shell>/bin/sh</shell>
<uid>2000</uid>
</user>
<nextuid>2001</nextuid>
<nextgid>2000</nextgid>
@@ -68,6 +68,7 @@
<compression/>
</webgui>
<usevirtualterminal>1</usevirtualterminal>
<disablenatreflection>yes</disablenatreflection>
<disableconsolemenu>1</disableconsolemenu>
<disablevlanhwfilter>1</disablevlanhwfilter>
<disablechecksumoffloading>1</disablechecksumoffloading>
@@ -103,6 +104,7 @@
<plugins>os-ddclient,os-dyndns,os-haproxy,os-wireguard</plugins>
<type/>
<subscription/>
<reboot/>
</firmware>
<sudo_allow_wheel>1</sudo_allow_wheel>
<sudo_allow_group>admins</sudo_allow_group>
@@ -837,6 +839,8 @@
<gateway/>
<carp_depend_on/>
<peers>03031aec-2e84-462e-9eab-57762dde667a,98e6ca3d-1de9-449b-be80-77022221b509,67c0ace5-e802-4d2b-a536-f8b7a2db6f99,74b60fff-7844-4097-9966-f1c2b1ad29ff,3de82ad5-bc1b-4b91-9598-f906e58ac937,a95e6b5e-24a4-40b5-bb41-b79e784f6f1c,6c9a12c6-c1ca-4c14-866b-975406a30590,c33b308b-7125-4688-9561-989ace8787b5,e43f004a-23bf-4027-8fb0-953fbb40479f</peers>
<endpoint/>
<peer_dns/>
</server>
</servers>
</server>
@@ -941,6 +945,7 @@
<root>0</root>
<maxConnections/>
<nbthread>1</nbthread>
<resolversPrefer>ipv4</resolversPrefer>
<sslServerVerify>ignore</sslServerVerify>
<maxDHSize>2048</maxDHSize>
<bufferSize>16384</bufferSize>
@@ -948,6 +953,9 @@
<bogusProxyEnabled>0</bogusProxyEnabled>
<luaMaxMem>0</luaMaxMem>
<customOptions/>
<ocspUpdateEnabled>0</ocspUpdateEnabled>
<ocspUpdateMinDelay>300</ocspUpdateMinDelay>
<ocspUpdateMaxDelay>3600</ocspUpdateMaxDelay>
<ssl_defaultsEnabled>0</ssl_defaultsEnabled>
<ssl_bindOptions>prefer-client-ciphers</ssl_bindOptions>
<ssl_minVersion>TLSv1.2</ssl_minVersion>

View File

@@ -68,6 +68,7 @@
<compression/>
</webgui>
<usevirtualterminal>1</usevirtualterminal>
<disablenatreflection>yes</disablenatreflection>
<disableconsolemenu>1</disableconsolemenu>
<disablevlanhwfilter>1</disablevlanhwfilter>
<disablechecksumoffloading>1</disablechecksumoffloading>
@@ -103,6 +104,7 @@
<plugins>os-ddclient,os-dyndns,os-haproxy,os-wireguard</plugins>
<type/>
<subscription/>
<reboot/>
</firmware>
<sudo_allow_wheel>1</sudo_allow_wheel>
<sudo_allow_group>admins</sudo_allow_group>
@@ -826,6 +828,8 @@
<dns/>
<tunneladdress>172.12.0.1/24</tunneladdress>
<disableroutes>0</disableroutes>
<endpoint/>
<peer_dns/>
<gateway/>
<carp_depend_on/>
<peers>03031aec-2e84-462e-9eab-57762dde667a,98e6ca3d-1de9-449b-be80-77022221b509,67c0ace5-e802-4d2b-a536-f8b7a2db6f99,74b60fff-7844-4097-9966-f1c2b1ad29ff,3de82ad5-bc1b-4b91-9598-f906e58ac937,a95e6b5e-24a4-40b5-bb41-b79e784f6f1c,6c9a12c6-c1ca-4c14-866b-975406a30590,c33b308b-7125-4688-9561-989ace8787b5,e43f004a-23bf-4027-8fb0-953fbb40479f</peers>
@@ -936,10 +940,14 @@
<sslServerVerify>ignore</sslServerVerify>
<maxDHSize>2048</maxDHSize>
<bufferSize>16384</bufferSize>
<resolversPrefer>ipv4</resolversPrefer>
<spreadChecks>2</spreadChecks>
<bogusProxyEnabled>0</bogusProxyEnabled>
<luaMaxMem>0</luaMaxMem>
<customOptions/>
<ocspUpdateEnabled>0</ocspUpdateEnabled>
<ocspUpdateMinDelay>300</ocspUpdateMinDelay>
<ocspUpdateMaxDelay>3600</ocspUpdateMaxDelay>
<ssl_defaultsEnabled>0</ssl_defaultsEnabled>
<ssl_bindOptions>prefer-client-ciphers</ssl_bindOptions>
<ssl_minVersion>TLSv1.2</ssl_minVersion>