From 0b30d82793767ae2e3b65d6676a27a451d4ebf04 Mon Sep 17 00:00:00 2001 From: Jean-Gabriel Gill-Couture Date: Tue, 4 Feb 2025 16:39:49 -0500 Subject: [PATCH] feat(harmony): add OPNSense shell command execution score Introduces a new `OPNSenseShellCommandScore` to execute shell commands on an OPNSense device within the Harmony framework. This allows for custom command execution as part of the scoring and interpretation process, enhancing the flexibility and functionality of the system. --- harmony-rs/examples/opnsense/src/main.rs | 5 ++ .../harmony/src/domain/interpret/mod.rs | 2 + harmony-rs/harmony/src/infra/opnsense/mod.rs | 6 +- harmony-rs/harmony/src/modules/mod.rs | 1 + harmony-rs/harmony/src/modules/opnsense.rs | 85 +++++++++++++++++++ .../opnsense-config/src/config/config.rs | 4 + 6 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 harmony-rs/harmony/src/modules/opnsense.rs diff --git a/harmony-rs/examples/opnsense/src/main.rs b/harmony-rs/examples/opnsense/src/main.rs index 723a55d..eda7b2f 100644 --- a/harmony-rs/examples/opnsense/src/main.rs +++ b/harmony-rs/examples/opnsense/src/main.rs @@ -13,6 +13,7 @@ use harmony::{ dummy::{ErrorScore, PanicScore, SuccessScore}, http::HttpScore, okd::{dhcp::OKDDhcpScore, dns::OKDDnsScore}, + opnsense::OPNSenseShellCommandScore, tftp::TftpScore, }, topology::{LogicalHost, UnmanagedRouter, Url}, @@ -91,6 +92,10 @@ async fn main() { Box::new(load_balancer_score), Box::new(tftp_score), Box::new(http_score), + Box::new(OPNSenseShellCommandScore { + opnsense: opnsense.get_opnsense_config(), + command: "touch /tmp/helloharmonytouching".to_string(), + }), Box::new(SuccessScore {}), Box::new(ErrorScore {}), Box::new(PanicScore {}), diff --git a/harmony-rs/harmony/src/domain/interpret/mod.rs b/harmony-rs/harmony/src/domain/interpret/mod.rs index 01ba62f..731d663 100644 --- a/harmony-rs/harmony/src/domain/interpret/mod.rs +++ b/harmony-rs/harmony/src/domain/interpret/mod.rs @@ -18,6 +18,7 @@ pub enum InterpretName { Http, Dummy, Panic, + OPNSense, } impl std::fmt::Display for InterpretName { @@ -30,6 +31,7 @@ impl std::fmt::Display for InterpretName { InterpretName::Http => f.write_str("Http"), InterpretName::Dummy => f.write_str("Dummy"), InterpretName::Panic => f.write_str("Panic"), + InterpretName::OPNSense => f.write_str("OPNSense"), } } } diff --git a/harmony-rs/harmony/src/infra/opnsense/mod.rs b/harmony-rs/harmony/src/infra/opnsense/mod.rs index 5d3f365..0aa5532 100644 --- a/harmony-rs/harmony/src/infra/opnsense/mod.rs +++ b/harmony-rs/harmony/src/infra/opnsense/mod.rs @@ -16,7 +16,7 @@ use crate::{ topology::{IpAddress, LogicalHost}, }; -#[derive(Clone)] +#[derive(Debug, Clone)] pub struct OPNSenseFirewall { opnsense_config: Arc>, host: LogicalHost, @@ -36,6 +36,10 @@ impl OPNSenseFirewall { } } + pub fn get_opnsense_config(&self) -> Arc> { + self.opnsense_config.clone() + } + async fn commit_config(&self) -> Result<(), ExecutorError> { self.opnsense_config .read() diff --git a/harmony-rs/harmony/src/modules/mod.rs b/harmony-rs/harmony/src/modules/mod.rs index dd5e177..c181375 100644 --- a/harmony-rs/harmony/src/modules/mod.rs +++ b/harmony-rs/harmony/src/modules/mod.rs @@ -5,4 +5,5 @@ pub mod http; pub mod k8s; pub mod load_balancer; pub mod okd; +pub mod opnsense; pub mod tftp; diff --git a/harmony-rs/harmony/src/modules/opnsense.rs b/harmony-rs/harmony/src/modules/opnsense.rs new file mode 100644 index 0000000..9ed1c2e --- /dev/null +++ b/harmony-rs/harmony/src/modules/opnsense.rs @@ -0,0 +1,85 @@ +use std::sync::Arc; + +use async_trait::async_trait; +use tokio::sync::RwLock; + +use crate::{ + data::{Id, Version}, + interpret::{Interpret, InterpretError, InterpretName, InterpretStatus, Outcome}, + inventory::Inventory, + score::Score, + topology::HAClusterTopology, +}; + +#[derive(Debug, Clone)] +pub struct OPNSenseShellCommandScore { + pub opnsense: Arc>, + pub command: String, +} + +impl Score for OPNSenseShellCommandScore { + fn create_interpret(&self) -> Box { + Box::new(OPNsenseInterpret { + status: InterpretStatus::QUEUED, + score: self.clone(), + }) + } + + fn name(&self) -> String { + "OPNSenseShellCommandScore".to_string() + } + + fn clone_box(&self) -> Box { + Box::new(self.clone()) + } +} + +#[derive(Debug)] +pub struct OPNsenseInterpret { + status: InterpretStatus, + score: OPNSenseShellCommandScore, +} + +#[async_trait] +impl Interpret for OPNsenseInterpret { + async fn execute( + &self, + _inventory: &Inventory, + _topology: &HAClusterTopology, + ) -> Result { + let output = self + .score + .opnsense + .read() + .await + .run_command(&self.score.command) + .await?; + + Ok(Outcome::success(format!( + "Command execution successful : {}\n\n{output}", + self.score.command + ))) + } + + fn get_name(&self) -> InterpretName { + InterpretName::OPNSense + } + + fn get_version(&self) -> Version { + Version::from("1.0.0").unwrap() + } + + fn get_status(&self) -> InterpretStatus { + self.status.clone() + } + + fn get_children(&self) -> Vec { + todo!() + } +} + +impl From for InterpretError { + fn from(value: opnsense_config::Error) -> Self { + Self::new(format!("opnsense_config::Error {value:?}")) + } +} diff --git a/harmony-rs/opnsense-config/src/config/config.rs b/harmony-rs/opnsense-config/src/config/config.rs index 614ebca..7250783 100644 --- a/harmony-rs/opnsense-config/src/config/config.rs +++ b/harmony-rs/opnsense-config/src/config/config.rs @@ -181,6 +181,10 @@ impl Config { Ok(OPNsense::from(xml)) } + + pub async fn run_command(&self, command: &str) -> Result { + self.shell.exec(command).await + } } #[cfg(test)]