feat: record results of beacon callbacks

This commit is contained in:
Andrew Rioux
2025-02-23 22:15:17 -05:00
parent 5ed8efca94
commit 7778e9b454
20 changed files with 446 additions and 136 deletions

View File

@@ -11,9 +11,9 @@ uuid = { version = "1.14.0", features = ["serde"] }
enum_delegate = "0.2.0"
async-trait = "0.1.86"
serde_json = "1.0.139"
thiserror = "2.0.11"
leptos = { version = "0.7.7", optional = true }
thiserror = { version = "2.0.11", optional = true }
pcap-sys = { path = "../pcap-sys", optional = true }
tokio = { version = "1.43.0", features = ["fs", "io-std", "io-util", "net", "process", "rt", "sync", "time", "tokio-macros"], optional = true }
smoltcp = { version = "0.12.0", default-features = false, features = ["proto-ipv4", "socket", "socket-tcp", "medium-ethernet", "std"], optional = true }
@@ -23,11 +23,25 @@ hyper-util = { version = "0.1.10", features = ["client", "client-legacy", "http1
hyper = { version = "1.6.0", features = ["client", "http1", "http2"], optional = true }
rustls = { version = "0.23.23", default-features = false, features = ["std"], optional = true }
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "sqlx-sqlite", "uuid"], optional = true }
bytes = { version = "1.10.0", optional = true }
http-body-util = { version = "0.1.2", optional = true }
[build-dependencies]
bindgen = "0.69"
[features]
beacon = ["dep:thiserror", "dep:pcap-sys", "dep:tokio", "dep:smoltcp", "dep:http", "dep:hyper-util", "dep:rustls", "dep:hyper", "dep:rmp-serde", "uuid/v4"]
beacon = [
"dep:pcap-sys",
"dep:tokio",
"dep:smoltcp",
"dep:http",
"dep:hyper-util",
"dep:rustls",
"dep:hyper",
"dep:rmp-serde",
"dep:bytes",
"dep:http-body-util",
"uuid/v4"
]
server-ssr = ["uuid/v4", "dep:sqlx"]
server = ["dep:leptos"]

View File

@@ -1,47 +1,106 @@
/// # Rules for actions:
/// Cannot have fields that have the following names:
/// `target_beacon_id`, `target_category_id`, or `cmd_type`
#[cfg(feature = "server")]
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::{payload_types::Parameters, adapter::BeaconAdapter, error::BeaconError};
use crate::version::Version;
mod ls;
mod update;
mod exec;
mod upload;
mod install;
mod download;
// mod ls;
// mod update;
// mod upload;
// mod install;
// mod download;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct FileId(pub uuid::Uuid);
/// Macro used to enforce the invariant that struct names are used to identify
/// the enum branch as well
macro_rules! define_actions_enum {
($(($mod:ident, $act:ident)),+) => {
#[derive(::serde::Serialize, ::serde::Deserialize)]
($(($mod:ident, $act:ident)),+$(,)?) => {
#[derive(::serde::Serialize, ::serde::Deserialize, Clone, Debug)]
#[serde(tag = "cmd_type")]
pub enum Actions {
$($act($mod::$act)),+,
}
$(
impl From<$mod::$act> for Actions {
fn from(act: $mod::$act) -> Self {
Self::$act(act)
}
}
)*
}
}
define_actions_enum! {
(ls, Ls),
(update, Update),
(exec, Exec),
(upload, Upload),
(install, Install),
(download, Download)
#[cfg(feature = "server-ssr")]
#[derive(thiserror::Error, Debug)]
pub enum BuildActionError {
#[error("sqlx error")]
Sqlx(#[from] sqlx::Error),
#[error("io error")]
Io(#[from] std::io::Error),
#[error("json error")]
Json(#[from] serde_json::Error)
}
define_actions_enum! {
(exec, Exec),
// (ls, Ls),
// (update, Update),
// (upload, Upload),
// (install, Install),
// (download, Download),
}
#[async_trait::async_trait]
#[cfg(feature = "beacon")]
impl Action for Actions {
const REQ_VERSION: Version = Version::new(2, 0);
const REQ_OS: Option< &'static str> = None;
const REQ_FIELDS: &'static[(&'static str, &'static str,Option< &'static str>)] = &[];
type ActionData = String;
async fn execute<'a, T, S>(
&self,
parameters: &Parameters,
adapter: &'a T,
client: &'a hyper_util::client::legacy::Client<S, http_body_util::Full<bytes::Bytes>>
) -> Result<Self::ActionData, BeaconError<T::Error>>
where
T: 'a + BeaconAdapter,
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
{
macro_rules! match_arm {
($cmd:expr) => {
$cmd
.execute(parameters, adapter, client)
.await
.and_then(|v| serde_json::to_string(&v)
.map_err(Into::into))
}
}
match self {
Actions::Exec(e) => match_arm!(e),
}
}
}
#[cfg(feature = "server")]
pub const ACTION_BUILDERS: &'static [&'static (dyn ActionBuilder + Send + Sync)] = &[
&ActionBuilderImpl::<ls::Ls>::new(),
&ActionBuilderImpl::<update::Update>::new(),
&ActionBuilderImpl::<exec::Exec>::new(),
&ActionBuilderImpl::<upload::Upload>::new(),
&ActionBuilderImpl::<install::Install>::new(),
&ActionBuilderImpl::<download::Download>::new(),
//&ActionBuilderImpl::<ls::Ls>::new(),
//&ActionBuilderImpl::<update::Update>::new(),
//&ActionBuilderImpl::<upload::Upload>::new(),
//&ActionBuilderImpl::<install::Install>::new(),
//&ActionBuilderImpl::<download::Download>::new(),
];
#[async_trait::async_trait]
@@ -51,33 +110,53 @@ pub trait Action: Serialize + for<'a> Deserialize<'a> {
const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)];
type ActionData: Serialize + for<'a> Deserialize<'a>;
#[cfg(feature = "server-ssr")]
type BuilderData: for<'a> Deserialize<'a>;
#[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData;
#[cfg(feature = "server-ssr")]
async fn build_action(data: Self::BuilderData, db: &sqlx::SqlitePool) -> Result<Self, BuildActionError>;
#[cfg(feature = "server")]
fn render_data(&self, data: Self::ActionData) -> impl IntoView;
#[cfg(feature = "beacon")]
async fn execute<'a, T, S>(
&self,
parameters: &Parameters,
adapter: &'a T,
client: &'a hyper_util::client::legacy::Client<S, http_body_util::Full<bytes::Bytes>>
) -> Result<Self::ActionData, BeaconError<T::Error>>
where
T: 'a + BeaconAdapter,
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static;
}
#[async_trait::async_trait]
#[cfg(feature = "server")]
pub trait ActionBuilder {
fn name(&self) -> &'static str;
fn required_version(&self) -> Version;
fn required_os(&self) -> Option<&'static str>;
fn form_elements(&self) -> &'static [(&'static str, &'static str, Option<&'static str>)];
fn verify_json_body(&self, body: serde_json::Value) -> Result<(), serde_json::Error>;
#[cfg(feature = "server-ssr")]
async fn build_action(&self, body: serde_json::Value, db: &sqlx::SqlitePool) -> Result<Actions, BuildActionError>;
}
#[cfg(feature = "server")]
pub struct ActionBuilderImpl<T>(std::marker::PhantomData<T>);
#[cfg(feature = "server")]
impl<T> ActionBuilderImpl<T> {
pub const fn new() -> Self {
Self(std::marker::PhantomData)
}
}
#[async_trait::async_trait]
#[cfg(feature = "server")]
impl<T> ActionBuilder for ActionBuilderImpl<T>
where
T: Action
T: Action, Actions: From<T>
{
fn name(&self) -> &'static str {
let tname = std::any::type_name::<T>();
@@ -92,7 +171,15 @@ where
fn form_elements(&self) -> &'static [(&'static str, &'static str, Option<&'static str>)] {
T::REQ_FIELDS
}
fn verify_json_body(&self, body: serde_json::Value) -> Result<(), serde_json::Error> {
serde_json::from_value::<T>(body).map(|_| ())
#[cfg(feature = "server-ssr")]
async fn build_action(&self, body: serde_json::Value, db: &sqlx::SqlitePool) -> Result<Actions, BuildActionError> {
let builder_data: T::BuilderData = serde_json::from_value(body)?;
let built_action = T::build_action(builder_data, db).await?;
Ok(built_action.into())
}
}
#[cfg(feature = "server")]
unsafe impl<T> Send for ActionBuilderImpl<T> {}
#[cfg(feature = "server")]
unsafe impl<T> Sync for ActionBuilderImpl<T> {}

View File

@@ -2,9 +2,11 @@
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::payload_types::Parameters;
use crate::version::Version;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct Download {
download_src: super::FileId,
download_path: String,
@@ -22,7 +24,7 @@ impl super::Action for Download {
type ActionData = ();
#[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData {
async fn execute(&self, _: &Parameters) -> Self::ActionData {
"Hi".to_string();
}

View File

@@ -2,9 +2,11 @@
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::{adapter::BeaconAdapter, error::BeaconError, payload_types::Parameters};
use crate::version::Version;
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Exec {
exec_cmd: String
}
@@ -18,10 +20,28 @@ impl super::Action for Exec {
];
type ActionData = String;
#[cfg(feature = "server-ssr")]
type BuilderData = Self;
#[cfg(feature = "server-ssr")]
async fn build_action(data: Self::BuilderData, _db: &sqlx::SqlitePool) -> Result<Self, super::BuildActionError> {
Ok(data)
}
#[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData {
"Execute".to_string()
async fn execute<'a, T, S>(
&self,
_parameters: &Parameters,
_adapter: &'a T,
_client: &'a hyper_util::client::legacy::Client<S, http_body_util::Full<bytes::Bytes>>
) -> Result<Self::ActionData, BeaconError<T::Error>>
where
T: 'a + BeaconAdapter,
S: hyper_util::client::legacy::connect::Connect + Clone + Send + Sync + 'static
{
println!("Execute command {}", self.exec_cmd);
Ok("Execute".to_string())
}
#[cfg(feature = "server")]

View File

@@ -2,9 +2,11 @@
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::payload_types::Parameters;
use crate::version::Version;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct Install {
install_target: std::path::PathBuf
}
@@ -20,7 +22,7 @@ impl super::Action for Install {
type ActionData = ();
#[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData {
async fn execute(&self, _: &Parameters) -> Self::ActionData {
"Hi".to_string();
}

View File

@@ -2,9 +2,11 @@
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::payload_types::Parameters;
use crate::version::Version;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct Ls;
#[async_trait::async_trait]
@@ -16,7 +18,7 @@ impl super::Action for Ls {
type ActionData = ();
#[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData {
async fn execute(&self, _: &Parameters) -> Self::ActionData {
"Hi".to_string();
}

View File

@@ -2,9 +2,11 @@
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::payload_types::Parameters;
use crate::version::Version;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct Update;
#[async_trait::async_trait]
@@ -16,7 +18,7 @@ impl super::Action for Update {
type ActionData = ();
#[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData {
async fn execute(&self, _: &Parameters) -> Self::ActionData {
"Hello".to_string();
}

View File

@@ -2,9 +2,11 @@
use leptos::prelude::*;
use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::payload_types::Parameters;
use crate::version::Version;
#[derive(Serialize, Deserialize)]
#[derive(Serialize, Deserialize, Clone)]
pub struct Upload {
upload_src: String
}
@@ -20,7 +22,7 @@ impl super::Action for Upload {
type ActionData = ();
#[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData {
async fn execute(&self, _: &Parameters) -> Self::ActionData {
"Hi".to_string();
}

View File

@@ -23,7 +23,7 @@ pub struct BeaconInterface {
}
#[async_trait::async_trait]
pub trait BeaconAdapter {
pub trait BeaconAdapter: Send + Sync {
type Error: error::AdapterError + Send + Sync;
const OPERATING_SYSTEM: &'static str;

View File

@@ -37,4 +37,6 @@ where
RmpSerdeDecode(#[from] rmp_serde::decode::Error),
#[error("http error")]
Hyper(#[from] hyper::Error),
#[error("serde json error")]
Json(#[from] serde_json::Error),
}

View File

@@ -10,6 +10,7 @@ pub struct RegisterBeacon {
pub operating_system: String,
pub userent: String,
pub hostname: String,
pub version: crate::version::Version,
}
#[derive(Clone, Serialize, Deserialize)]
@@ -37,4 +38,10 @@ pub enum RuntimeConfig {
#[derive(Clone, Serialize, Deserialize)]
pub struct BeaconConfig {
pub runtime_config: RuntimeConfig,
pub unfinished_actions: Vec<(i64, crate::actions::Actions)>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct CommandInvocationResult {
pub result_body: String,
}