All checks were successful
Run Check Script / check (pull_request) Successful in 59s
Co-authored-by: Jean-Gabriel Gill-Couture <jeangabriel.gc@gmail.com> Co-authored-by: Ian Letourneau <ian@noma.to> Reviewed-on: https://git.nationtech.io/NationTech/harmony/pulls/130 Reviewed-by: Ian Letourneau <ian@noma.to> Co-authored-by: Jean-Gabriel Gill-Couture <jg@nationtech.io> Co-committed-by: Jean-Gabriel Gill-Couture <jg@nationtech.io>
98 lines
2.9 KiB
Rust
98 lines
2.9 KiB
Rust
use crate::config::{manager::ConfigManager, OPNsenseShell};
|
|
use crate::error::Error;
|
|
use async_trait::async_trait;
|
|
use log::{info, warn};
|
|
use russh_keys::key::KeyPair;
|
|
use sha2::Digest;
|
|
use std::sync::Arc;
|
|
|
|
#[derive(Debug)]
|
|
pub enum SshCredentials {
|
|
SshKey { username: String, key: Arc<KeyPair> },
|
|
Password { username: String, password: String },
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct SshConfigManager {
|
|
opnsense_shell: Arc<dyn OPNsenseShell>,
|
|
}
|
|
|
|
impl SshConfigManager {
|
|
pub fn new(opnsense_shell: Arc<dyn OPNsenseShell>) -> Self {
|
|
Self { opnsense_shell }
|
|
}
|
|
}
|
|
|
|
impl SshConfigManager {
|
|
async fn backup_config_remote(&self) -> Result<String, Error> {
|
|
let ts = chrono::Utc::now();
|
|
let backup_filename = format!("config-{}-harmony.xml", ts.format("%s%.3f"));
|
|
|
|
self.opnsense_shell
|
|
.exec(&format!(
|
|
"cp /conf/config.xml /conf/backup/{}",
|
|
backup_filename
|
|
))
|
|
.await
|
|
}
|
|
|
|
async fn copy_to_live_config(&self, new_config_path: &str) -> Result<String, Error> {
|
|
info!("Overwriting OPNSense /conf/config.xml with {new_config_path}");
|
|
self.opnsense_shell
|
|
.exec(&format!("cp {new_config_path} /conf/config.xml"))
|
|
.await
|
|
}
|
|
|
|
async fn reload_all_services(&self) -> Result<String, Error> {
|
|
info!("Reloading all opnsense services");
|
|
self.opnsense_shell
|
|
.exec("configctl service reload all")
|
|
.await
|
|
}
|
|
}
|
|
|
|
#[async_trait]
|
|
impl ConfigManager for SshConfigManager {
|
|
async fn load_as_str(&self) -> Result<String, Error> {
|
|
self.opnsense_shell.exec("cat /conf/config.xml").await
|
|
}
|
|
|
|
async fn save_config(&self, content: &str, hash: &str) -> Result<(), Error> {
|
|
let current_content = self.load_as_str().await?;
|
|
|
|
if !check_hash(¤t_content, hash) {
|
|
warn!("OPNSense config file changed since loading it! Hash when loading : {hash}");
|
|
// return Err(Error::Config(format!(
|
|
// "OPNSense config file changed since loading it! Hash when loading : {hash}"
|
|
// )));
|
|
}
|
|
|
|
let temp_filename = self
|
|
.opnsense_shell
|
|
.write_content_to_temp_file(content)
|
|
.await?;
|
|
self.backup_config_remote().await?;
|
|
self.copy_to_live_config(&temp_filename).await?;
|
|
Ok(())
|
|
}
|
|
|
|
async fn apply_new_config(&self, content: &str, hash: &str) -> Result<(), Error> {
|
|
self.save_config(content, &hash).await?;
|
|
self.reload_all_services().await?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
pub fn get_hash(content: &str) -> String {
|
|
let mut hasher = sha2::Sha256::new();
|
|
hasher.update(content.as_bytes());
|
|
let hash_bytes = hasher.finalize();
|
|
let hash_string = format!("{:x}", hash_bytes);
|
|
info!("Loaded OPNSense config.xml with hash {hash_string:?}");
|
|
hash_string
|
|
}
|
|
|
|
pub fn check_hash(content: &str, source_hash: &str) -> bool {
|
|
get_hash(content) == source_hash
|
|
}
|