feat: event management and websocket for updates
This commit is contained in:
parent
005048f1ce
commit
faaa4d2d1a
73
Cargo.lock
generated
73
Cargo.lock
generated
@ -329,6 +329,19 @@ dependencies = [
|
|||||||
"syn 2.0.98",
|
"syn 2.0.98",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "axum-msgpack"
|
||||||
|
version = "0.4.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1dd328722bb555cc4e7306591aae2d80be83b64c71b183b0e9dd9a16412dc55a"
|
||||||
|
dependencies = [
|
||||||
|
"axum",
|
||||||
|
"hyper",
|
||||||
|
"mime",
|
||||||
|
"rmp-serde",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "axum-server"
|
name = "axum-server"
|
||||||
version = "0.7.1"
|
version = "0.7.1"
|
||||||
@ -545,6 +558,8 @@ version = "0.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5d3ad3122b0001c7f140cf4d605ef9a9e2c24d96ab0b4fb4347b76de2425f445"
|
checksum = "5d3ad3122b0001c7f140cf4d605ef9a9e2c24d96ab0b4fb4347b76de2425f445"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"thiserror 1.0.69",
|
"thiserror 1.0.69",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2891,6 +2906,28 @@ dependencies = [
|
|||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp"
|
||||||
|
version = "0.8.14"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "228ed7c16fa39782c3b3468e974aec2795e9089153cd08ee2e9aefb3613334c4"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"num-traits",
|
||||||
|
"paste",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "rmp-serde"
|
||||||
|
version = "1.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "52e599a477cf9840e92f2cde9a7189e67b42c57532749bf90aea6ec10facd4db"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"rmp",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "rpassword"
|
name = "rpassword"
|
||||||
version = "7.3.1"
|
version = "7.3.1"
|
||||||
@ -3122,18 +3159,27 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde"
|
name = "serde"
|
||||||
version = "1.0.217"
|
version = "1.0.218"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
|
checksum = "e8dfc9d19bdbf6d17e22319da49161d5d0108e4188e8b680aef6299eed22df60"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_derive"
|
name = "serde_bytes"
|
||||||
version = "1.0.217"
|
version = "0.11.15"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
|
checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a"
|
||||||
|
dependencies = [
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "serde_derive"
|
||||||
|
version = "1.0.218"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f09503e191f4e797cb8aac08e9a4a4695c5edf6a2e70e376d961ddd5c969f82b"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -3142,9 +3188,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "serde_json"
|
name = "serde_json"
|
||||||
version = "1.0.138"
|
version = "1.0.139"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "d434192e7da787e94a6ea7e9670b26a036d0ca41e0b7efb2676dd32bae872949"
|
checksum = "44f86c3acccc9c65b153fe1b85a3be07fe5515274ec9f0653b4a0875731c72a6"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"itoa",
|
"itoa",
|
||||||
"memchr",
|
"memchr",
|
||||||
@ -3379,6 +3425,9 @@ name = "sparse-actions"
|
|||||||
version = "2.0.0"
|
version = "2.0.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bindgen",
|
"bindgen",
|
||||||
|
"chrono",
|
||||||
|
"serde",
|
||||||
|
"serde_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -3387,8 +3436,10 @@ version = "0.7.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
|
"cron",
|
||||||
"futures",
|
"futures",
|
||||||
"http",
|
"http",
|
||||||
|
"http-body",
|
||||||
"http-body-util",
|
"http-body-util",
|
||||||
"hyper",
|
"hyper",
|
||||||
"hyper-rustls",
|
"hyper-rustls",
|
||||||
@ -3397,8 +3448,11 @@ dependencies = [
|
|||||||
"pcap-sys",
|
"pcap-sys",
|
||||||
"pin-project",
|
"pin-project",
|
||||||
"rand 0.9.0",
|
"rand 0.9.0",
|
||||||
|
"rmp-serde",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-openssl",
|
"rustls-openssl",
|
||||||
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"simple_logger",
|
"simple_logger",
|
||||||
"smoltcp",
|
"smoltcp",
|
||||||
"sparse-actions",
|
"sparse-actions",
|
||||||
@ -3414,13 +3468,16 @@ version = "2.0.0"
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
|
"axum-msgpack",
|
||||||
"axum-server",
|
"axum-server",
|
||||||
|
"chrono",
|
||||||
"futures",
|
"futures",
|
||||||
"rcgen",
|
"rcgen",
|
||||||
"rustls",
|
"rustls",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
"sparse-actions",
|
||||||
"sqlx",
|
"sqlx",
|
||||||
"tokio",
|
"tokio",
|
||||||
"tokio-stream",
|
"tokio-stream",
|
||||||
@ -3454,7 +3511,9 @@ dependencies = [
|
|||||||
"rcgen",
|
"rcgen",
|
||||||
"rpassword",
|
"rpassword",
|
||||||
"rustls-pki-types",
|
"rustls-pki-types",
|
||||||
|
"send_wrapper",
|
||||||
"serde",
|
"serde",
|
||||||
|
"serde_json",
|
||||||
"sha2",
|
"sha2",
|
||||||
"sparse-actions",
|
"sparse-actions",
|
||||||
"sparse-handler",
|
"sparse-handler",
|
||||||
|
|||||||
@ -4,6 +4,9 @@ edition = "2021"
|
|||||||
version.workspace = true
|
version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
chrono = { version = "0.4.39", features = ["serde"] }
|
||||||
|
serde = { version = "1.0.218", features = ["derive"] }
|
||||||
|
serde_bytes = "0.11.15"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
bindgen = "0.69"
|
bindgen = "0.69"
|
||||||
|
|||||||
1
sparse-actions/src/actions.rs
Normal file
1
sparse-actions/src/actions.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
pub trait Action {}
|
||||||
@ -3,3 +3,6 @@
|
|||||||
pub mod payload_types {
|
pub mod payload_types {
|
||||||
include!(concat!(std::env!("OUT_DIR"), "/bindings.rs"));
|
include!(concat!(std::env!("OUT_DIR"), "/bindings.rs"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub mod actions;
|
||||||
|
pub mod messages;
|
||||||
|
|||||||
40
sparse-actions/src/messages.rs
Normal file
40
sparse-actions/src/messages.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct RegisterBeacon {
|
||||||
|
pub beacon_id: String,
|
||||||
|
pub template_id: u16,
|
||||||
|
pub cwd: PathBuf,
|
||||||
|
pub operating_system: String,
|
||||||
|
pub userent: String,
|
||||||
|
pub hostname: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub enum CronTimezone {
|
||||||
|
Utc,
|
||||||
|
Local,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub enum RuntimeConfig {
|
||||||
|
Oneshot,
|
||||||
|
Random {
|
||||||
|
interval_min: u64,
|
||||||
|
interval_max: u64,
|
||||||
|
},
|
||||||
|
Regular {
|
||||||
|
interval: u64,
|
||||||
|
},
|
||||||
|
Cron {
|
||||||
|
schedule: String,
|
||||||
|
timezone: CronTimezone,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub struct BeaconConfig {
|
||||||
|
pub runtime_config: RuntimeConfig,
|
||||||
|
}
|
||||||
@ -8,7 +8,7 @@ publish = false
|
|||||||
hyper = { version = "1.6.0", features = ["client", "http1", "http2"] }
|
hyper = { version = "1.6.0", features = ["client", "http1", "http2"] }
|
||||||
smoltcp = { version = "0.12.0", default-features = false, features = ["async", "log", "medium-ethernet", "proto-ipv4", "proto-ipv4-fragmentation", "socket-raw", "socket-tcp", "std"] }
|
smoltcp = { version = "0.12.0", default-features = false, features = ["async", "log", "medium-ethernet", "proto-ipv4", "proto-ipv4-fragmentation", "socket-raw", "socket-tcp", "std"] }
|
||||||
thiserror = "2.0.11"
|
thiserror = "2.0.11"
|
||||||
tokio = { version = "1.43.0", features = ["fs", "io-std", "io-util", "net", "process", "rt", "sync", "tokio-macros"] }
|
tokio = { version = "1.43.0", features = ["fs", "io-std", "io-util", "net", "process", "rt", "sync", "time", "tokio-macros"] }
|
||||||
async-trait = "0.1.86"
|
async-trait = "0.1.86"
|
||||||
tracing = "0.1.41"
|
tracing = "0.1.41"
|
||||||
rand = "0.9.0"
|
rand = "0.9.0"
|
||||||
@ -27,6 +27,11 @@ http-body-util = "0.1.2"
|
|||||||
pcap-sys = { version = "0.1.0", path = "../pcap-sys" }
|
pcap-sys = { version = "0.1.0", path = "../pcap-sys" }
|
||||||
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||||
packets = { version = "0.1.0", path = "../packets" }
|
packets = { version = "0.1.0", path = "../packets" }
|
||||||
|
serde_json = "1.0.139"
|
||||||
|
serde = "1.0.217"
|
||||||
|
http-body = "1.0.1"
|
||||||
|
rmp-serde = "1.3.0"
|
||||||
|
cron = "0.15.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
openssl = ["dep:rustls-openssl"]
|
openssl = ["dep:rustls-openssl"]
|
||||||
|
|||||||
@ -26,7 +26,13 @@ pub struct BeaconInterface {
|
|||||||
pub trait BeaconAdapter {
|
pub trait BeaconAdapter {
|
||||||
type Error: error::AdapterError + Send + Sync;
|
type Error: error::AdapterError + Send + Sync;
|
||||||
|
|
||||||
|
const OPERATING_SYSTEM: &'static str;
|
||||||
|
|
||||||
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8>;
|
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8>;
|
||||||
|
|
||||||
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError<Self::Error>>;
|
fn networking_info(&self) -> Result<BeaconNetworkingInfo, error::BeaconError<Self::Error>>;
|
||||||
|
|
||||||
|
async fn get_username(&self) -> Result<String, error::BeaconError<Self::Error>>;
|
||||||
|
|
||||||
|
async fn get_hostname(&self) -> Result<String, error::BeaconError<Self::Error>>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,6 +52,8 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type SClient<T, B> = Client<hyper_rustls::HttpsConnector<ServerConnector<T>>, B>;
|
||||||
|
|
||||||
pub async fn obtain_https_client<T, B>(
|
pub async fn obtain_https_client<T, B>(
|
||||||
adapter: &T,
|
adapter: &T,
|
||||||
parameters: &Parameters,
|
parameters: &Parameters,
|
||||||
|
|||||||
@ -29,4 +29,12 @@ where
|
|||||||
Rustls(#[from] rustls::Error),
|
Rustls(#[from] rustls::Error),
|
||||||
#[error("adapter error")]
|
#[error("adapter error")]
|
||||||
Adapter(#[from] T),
|
Adapter(#[from] T),
|
||||||
|
#[error("http error from server")]
|
||||||
|
SparseServerHttpError(http::StatusCode),
|
||||||
|
#[error("message pack encode error")]
|
||||||
|
RmpSerdeEncode(#[from] rmp_serde::encode::Error),
|
||||||
|
#[error("message pack decode error")]
|
||||||
|
RmpSerdeDecode(#[from] rmp_serde::decode::Error),
|
||||||
|
#[error("http error")]
|
||||||
|
Hyper(#[from] hyper::Error),
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,14 @@
|
|||||||
use sparse_actions::payload_types::Parameters;
|
use sparse_actions::payload_types::Parameters;
|
||||||
|
|
||||||
use http_body_util::{BodyExt, Empty};
|
use http_body_util::{BodyExt, Full};
|
||||||
use hyper::Request;
|
use hyper::{Request, Method};
|
||||||
|
|
||||||
|
use sparse_actions::messages;
|
||||||
|
|
||||||
mod callback;
|
mod callback;
|
||||||
mod socket;
|
mod socket;
|
||||||
mod tcp;
|
mod tcp;
|
||||||
|
mod params;
|
||||||
|
|
||||||
pub mod adapter;
|
pub mod adapter;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
@ -19,6 +22,37 @@ 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>(
|
||||||
|
client: &callback::SClient<A, Full<bytes::Bytes>>,
|
||||||
|
uri: hyper::Uri,
|
||||||
|
req_body: Req
|
||||||
|
) -> Result<Resp, 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,
|
||||||
|
{
|
||||||
|
let mut body_buf = Vec::new();
|
||||||
|
req_body.serialize(&mut rmp_serde::Serializer::new(&mut body_buf))?;
|
||||||
|
|
||||||
|
let req = Request::builder()
|
||||||
|
.method(Method::POST)
|
||||||
|
.uri(uri)
|
||||||
|
.header("content-type", "application/msgpack")
|
||||||
|
.body(Full::<bytes::Bytes>::from(body_buf))?;
|
||||||
|
|
||||||
|
let resp = client.request(req).await?;
|
||||||
|
|
||||||
|
if !resp.status().is_success() {
|
||||||
|
return Err(BeaconError::SparseServerHttpError(resp.status()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let body = resp.into_body();
|
||||||
|
let body = body.collect().await?;
|
||||||
|
|
||||||
|
rmp_serde::from_slice(&body.to_bytes()).map_err(Into::into)
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn run_beacon_step<A>(
|
pub async fn run_beacon_step<A>(
|
||||||
host_adapter: A,
|
host_adapter: A,
|
||||||
params: Parameters,
|
params: Parameters,
|
||||||
@ -26,19 +60,54 @@ pub async fn run_beacon_step<A>(
|
|||||||
where
|
where
|
||||||
A: adapter::BeaconAdapter + Clone + Send + Sync + 'static,
|
A: adapter::BeaconAdapter + Clone + Send + Sync + '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 mut config: messages::BeaconConfig = {
|
||||||
let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
let client = callback::obtain_https_client(&host_adapter, ¶ms).await?;
|
||||||
|
|
||||||
for _ in 1..5 {
|
dbg!(¶ms.beacon_identifier);
|
||||||
let req = Request::builder()
|
|
||||||
.uri("https://sparse.com/hidden_sparse/test".parse::<hyper::Uri>()?)
|
|
||||||
.body(Empty::<bytes::Bytes>::new())?;
|
|
||||||
let resp = client.request(req).await?;
|
|
||||||
|
|
||||||
println!("{:?} {:?}", resp.version(), resp.status());
|
make_request(
|
||||||
let body = resp.into_body();
|
&client,
|
||||||
let body = body.collect().await;
|
format!("https://{}/checkin", params::domain_name::<A>(¶ms)?).parse()?,
|
||||||
println!("{:?}", body);
|
messages::RegisterBeacon {
|
||||||
|
beacon_id: std::str::from_utf8(¶ms.beacon_identifier)?.to_owned(),
|
||||||
|
template_id: params.template_id,
|
||||||
|
cwd: std::env::current_dir()?,
|
||||||
|
operating_system: A::OPERATING_SYSTEM.to_string(),
|
||||||
|
userent: userent.clone(),
|
||||||
|
hostname: hostname.clone()
|
||||||
}
|
}
|
||||||
|
).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 } => {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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?;
|
||||||
|
|
||||||
|
// println!("{:?} {:?}", resp.version(), resp.status());
|
||||||
|
// let body = resp.into_body();
|
||||||
|
// let body = body.collect().await;
|
||||||
|
// println!("{:?}", body);
|
||||||
|
// }
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
12
sparse-beacon/src/params.rs
Normal file
12
sparse-beacon/src/params.rs
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
use sparse_actions::payload_types::Parameters;
|
||||||
|
|
||||||
|
use crate::adapter::BeaconAdapter;
|
||||||
|
use crate::error::BeaconError;
|
||||||
|
|
||||||
|
pub fn domain_name<'a, T>(params: &'a Parameters) -> Result<&'a str, BeaconError<T::Error>>
|
||||||
|
where
|
||||||
|
T: BeaconAdapter,
|
||||||
|
{
|
||||||
|
std::str::from_utf8(¶ms.domain_name[..params.domain_name_length as usize])
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
21
sparse-beacon/src/prelude.rs
Normal file
21
sparse-beacon/src/prelude.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use sparse_actions::payload_types::Parameters;
|
||||||
|
|
||||||
|
use crate::adapter::BeaconAdapter;
|
||||||
|
use crate::error::{AdapterError, BeaconError};
|
||||||
|
|
||||||
|
pub trait UsableParameters<T>
|
||||||
|
where
|
||||||
|
T: BeaconAdapter,
|
||||||
|
{
|
||||||
|
fn domain_name_str(&self) -> Result<&str, BeaconError<T::Error>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> UsableParameters<T> for Parameters
|
||||||
|
where
|
||||||
|
T: BeaconAdapter,
|
||||||
|
{
|
||||||
|
fn domain_name_str(&self) -> Result<&str, BeaconError<T::Error>> {
|
||||||
|
std::str::from_utf8(&self.domain_name[..self.domain_name_length as usize])
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -25,8 +25,6 @@ impl RawSocket {
|
|||||||
|
|
||||||
let mtu = a_interface.mtu as usize + if cfg!(unix) { 14 } else { 0 };
|
let mtu = a_interface.mtu as usize + if cfg!(unix) { 14 } else { 0 };
|
||||||
|
|
||||||
dbg!(promisc);
|
|
||||||
|
|
||||||
lower.set_promisc(promisc)?;
|
lower.set_promisc(promisc)?;
|
||||||
lower.set_buffer_size(mtu as i32)?;
|
lower.set_buffer_size(mtu as i32)?;
|
||||||
lower.set_non_blocking(true)?;
|
lower.set_non_blocking(true)?;
|
||||||
|
|||||||
50
sparse-handler/.sqlx/query-04472775affd2b1694c05f5ab8125528bb5448e0378a7b9cf3ed58eaa2101d1e.json
generated
Normal file
50
sparse-handler/.sqlx/query-04472775affd2b1694c05f5ab8125528bb5448e0378a7b9cf3ed58eaa2101d1e.json
generated
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT c.mode, c.regular_interval, c.random_min_time, c.random_max_time, c.cron_schedule, c.cron_mode\n FROM beacon_template t\n INNER JOIN beacon_config c ON c.config_id = t.config_id\n WHERE t.template_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "regular_interval",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_min_time",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_max_time",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cron_schedule",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cron_mode",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "04472775affd2b1694c05f5ab8125528bb5448e0378a7b9cf3ed58eaa2101d1e"
|
||||||
|
}
|
||||||
12
sparse-handler/.sqlx/query-6e5e4ddd5c8b60c2180ca4cadf1449dc8b8b37a8887cd5b01f1ca9d0a411b897.json
generated
Normal file
12
sparse-handler/.sqlx/query-6e5e4ddd5c8b60c2180ca4cadf1449dc8b8b37a8887cd5b01f1ca9d0a411b897.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT INTO beacon_checkin (beacon_id, checkin_date) VALUES (?, ?)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "6e5e4ddd5c8b60c2180ca4cadf1449dc8b8b37a8887cd5b01f1ca9d0a411b897"
|
||||||
|
}
|
||||||
50
sparse-handler/.sqlx/query-bb07fb691f373dea848c0368d4c36e4c2b079d2d1efc0006d43d6833023101a3.json
generated
Normal file
50
sparse-handler/.sqlx/query-bb07fb691f373dea848c0368d4c36e4c2b079d2d1efc0006d43d6833023101a3.json
generated
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT c.mode as mode, c.regular_interval as regular_interval, c.random_min_time as random_min_time,\n c.random_max_time as random_max_time, c.cron_schedule as cron_schedule, c.cron_mode as cron_mode\n FROM beacon_instance i\n INNER JOIN beacon_config c ON c.config_id = i.config_id\n WHERE i.beacon_id = ?\n UNION\n SELECT c.mode as mode, c.regular_interval as regular_interval, c.random_min_time as random_min_time,\n c.random_max_time as random_max_time, c.cron_schedule as cron_schedule, c.cron_mode as cron_mode\n FROM beacon_instance i\n INNER JOIN beacon_template t ON i.template_id = t.template_id\n INNER JOIN beacon_config c ON t.config_id = c.config_id\n WHERE i.beacon_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "regular_interval",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_min_time",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_max_time",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cron_schedule",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cron_mode",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "bb07fb691f373dea848c0368d4c36e4c2b079d2d1efc0006d43d6833023101a3"
|
||||||
|
}
|
||||||
@ -1,36 +1,26 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT * FROM beacon_listener WHERE listener_id = ?",
|
"query": "SELECT port, domain_name, certificate, privkey FROM beacon_listener WHERE listener_id = ?",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "listener_id",
|
"name": "port",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "port",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Integer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "public_ip",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "domain_name",
|
"name": "domain_name",
|
||||||
"ordinal": 3,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "certificate",
|
"name": "certificate",
|
||||||
"ordinal": 4,
|
"ordinal": 2,
|
||||||
"type_info": "Blob"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "privkey",
|
"name": "privkey",
|
||||||
"ordinal": 5,
|
"ordinal": 3,
|
||||||
"type_info": "Blob"
|
"type_info": "Blob"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -38,13 +28,11 @@
|
|||||||
"Right": 1
|
"Right": 1
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "e7dc753795b8976b14b5c4baec20d16eff715a4d2ffe93c6723bad368483fb69"
|
"hash": "f130d1b3b891a4f5d57b69bcf111230b85d7fb636517e825a42c2a59fc8b8311"
|
||||||
}
|
}
|
||||||
12
sparse-handler/.sqlx/query-fa45ebf8fb26791336d5ab3701a3fc6fdf17d3eaddb0e5cff099e0396fe4dddd.json
generated
Normal file
12
sparse-handler/.sqlx/query-fa45ebf8fb26791336d5ab3701a3fc6fdf17d3eaddb0e5cff099e0396fe4dddd.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT INTO beacon_instance\n (beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname)\n VALUES\n (?, ?, ?, \"\", ?, ?, ?, ?)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 7
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "fa45ebf8fb26791336d5ab3701a3fc6fdf17d3eaddb0e5cff099e0396fe4dddd"
|
||||||
|
}
|
||||||
@ -15,5 +15,8 @@ serde = "1.0"
|
|||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
axum-server = { version = "^0.7", features = ["tokio-rustls", "tls-rustls-no-provider"] }
|
axum-server = { version = "^0.7", features = ["tokio-rustls", "tls-rustls-no-provider"] }
|
||||||
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
|
rustls = { version = "0.23", default-features = false, features = ["ring", "std"] }
|
||||||
rcgen = "0.13.2"
|
rcgen = { version = "0.13.2", features = ["pem", "x509-parser", "crypto"] }
|
||||||
rustls-pki-types = "1.11.0"
|
rustls-pki-types = "1.11.0"
|
||||||
|
axum-msgpack = "0.4.0"
|
||||||
|
sparse-actions = { version = "2.0.0", path = "../sparse-actions" }
|
||||||
|
chrono = { version = "0.4.39", features = ["serde"] }
|
||||||
|
|||||||
@ -1,3 +1,8 @@
|
|||||||
|
use axum::{
|
||||||
|
http::StatusCode,
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
Generic(String),
|
Generic(String),
|
||||||
@ -51,6 +56,12 @@ impl std::error::Error for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl IntoResponse for Error {
|
||||||
|
fn into_response(self) -> Response {
|
||||||
|
(StatusCode::INTERNAL_SERVER_ERROR, format!("{self}")).into_response()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl std::str::FromStr for Error {
|
impl std::str::FromStr for Error {
|
||||||
type Err = Self;
|
type Err = Self;
|
||||||
|
|
||||||
|
|||||||
@ -1,18 +1,25 @@
|
|||||||
use std::{
|
use std::{
|
||||||
collections::HashMap,
|
collections::HashMap, net::SocketAddr, sync::{Arc, RwLock}
|
||||||
sync::{Arc, RwLock},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use axum::routing::{Router, get, post};
|
use rcgen::{CertificateParams, KeyPair};
|
||||||
use rcgen::{Certificate, CertificateParams, KeyPair};
|
|
||||||
use rustls::{RootCertStore, server::WebPkiClientVerifier};
|
use rustls::{RootCertStore, server::WebPkiClientVerifier};
|
||||||
use sqlx::SqlitePool;
|
use sqlx::SqlitePool;
|
||||||
use tokio::task::JoinHandle;
|
use tokio::{sync::broadcast, task::JoinHandle};
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
|
||||||
|
mod router;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum BeaconEvent {
|
||||||
|
NewBeacon(String),
|
||||||
|
Checkin(String)
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BeaconListenerHandle {
|
pub struct BeaconListenerHandle {
|
||||||
join_handle: JoinHandle<()>,
|
join_handle: JoinHandle<()>,
|
||||||
|
events_broadcast: broadcast::Sender<BeaconEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BeaconListenerHandle {
|
impl BeaconListenerHandle {
|
||||||
@ -23,6 +30,10 @@ impl BeaconListenerHandle {
|
|||||||
pub fn abort(&self) {
|
pub fn abort(&self) {
|
||||||
self.join_handle.abort()
|
self.join_handle.abort()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn event_subscribe(&self) -> broadcast::Receiver<BeaconEvent> {
|
||||||
|
self.events_broadcast.subscribe()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default)]
|
#[derive(Clone, Default)]
|
||||||
@ -39,6 +50,7 @@ impl std::ops::Deref for BeaconListenerMap {
|
|||||||
pub async fn start_all_listeners(
|
pub async fn start_all_listeners(
|
||||||
beacon_listener_map: BeaconListenerMap,
|
beacon_listener_map: BeaconListenerMap,
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
|
beacon_event_broadcast: tokio::sync::broadcast::Sender::<BeaconEvent>
|
||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
rustls::crypto::ring::default_provider().install_default().expect("could not set up rustls");
|
rustls::crypto::ring::default_provider().install_default().expect("could not set up rustls");
|
||||||
|
|
||||||
@ -53,6 +65,7 @@ pub async fn start_all_listeners(
|
|||||||
beacon_listener_map.clone(),
|
beacon_listener_map.clone(),
|
||||||
listener.listener_id,
|
listener.listener_id,
|
||||||
db.clone(),
|
db.clone(),
|
||||||
|
beacon_event_broadcast.clone(),
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
@ -60,15 +73,8 @@ pub async fn start_all_listeners(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct ListenerState {
|
|
||||||
db: SqlitePool,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Listener {
|
struct Listener {
|
||||||
listener_id: i64,
|
|
||||||
port: i64,
|
port: i64,
|
||||||
public_ip: String,
|
|
||||||
domain_name: String,
|
domain_name: String,
|
||||||
certificate: Vec<u8>,
|
certificate: Vec<u8>,
|
||||||
privkey: Vec<u8>,
|
privkey: Vec<u8>,
|
||||||
@ -78,6 +84,7 @@ pub async fn start_listener(
|
|||||||
beacon_listener_map: BeaconListenerMap,
|
beacon_listener_map: BeaconListenerMap,
|
||||||
listener_id: i64,
|
listener_id: i64,
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
|
beacon_event_broadcast: tokio::sync::broadcast::Sender::<BeaconEvent>
|
||||||
) -> Result<(), crate::error::Error> {
|
) -> Result<(), crate::error::Error> {
|
||||||
{
|
{
|
||||||
let Ok(blm_handle) = beacon_listener_map.read() else {
|
let Ok(blm_handle) = beacon_listener_map.read() else {
|
||||||
@ -94,29 +101,15 @@ pub async fn start_listener(
|
|||||||
}
|
}
|
||||||
let listener = sqlx::query_as!(
|
let listener = sqlx::query_as!(
|
||||||
Listener,
|
Listener,
|
||||||
"SELECT * FROM beacon_listener WHERE listener_id = ?",
|
"SELECT port, domain_name, certificate, privkey FROM beacon_listener WHERE listener_id = ?",
|
||||||
listener_id
|
listener_id
|
||||||
)
|
)
|
||||||
.fetch_one(&db)
|
.fetch_one(&db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let app: Router<()> = Router::new()
|
let sender = broadcast::Sender::new(128);
|
||||||
.route(
|
|
||||||
"/register_beacon",
|
|
||||||
post(|| async {
|
|
||||||
tracing::info!("Beacon attempting to register");
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.route(
|
|
||||||
"/test",
|
|
||||||
get(|| async {
|
|
||||||
tracing::info!("Hello");
|
|
||||||
"hi there"
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
.with_state(ListenerState { db });
|
|
||||||
|
|
||||||
let hidden_app = Router::new().nest("/hidden_sparse", app);
|
let app = router::get_router(db, sender.clone());
|
||||||
|
|
||||||
let ca_cert = rustls::pki_types::CertificateDer::from(listener.certificate.clone());
|
let ca_cert = rustls::pki_types::CertificateDer::from(listener.certificate.clone());
|
||||||
|
|
||||||
@ -175,7 +168,7 @@ pub async fn start_listener(
|
|||||||
addr,
|
addr,
|
||||||
axum_server::tls_rustls::RustlsConfig::from_config(Arc::new(tls_config)),
|
axum_server::tls_rustls::RustlsConfig::from_config(Arc::new(tls_config)),
|
||||||
)
|
)
|
||||||
.serve(hidden_app.into_make_service())
|
.serve(app.into_make_service_with_connect_info::<SocketAddr>())
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
if let Err(e) = res {
|
if let Err(e) = res {
|
||||||
@ -189,7 +182,7 @@ pub async fn start_listener(
|
|||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
blm_handle.insert(listener_id, BeaconListenerHandle { join_handle });
|
blm_handle.insert(listener_id, BeaconListenerHandle { join_handle, events_broadcast: sender });
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
149
sparse-handler/src/router.rs
Normal file
149
sparse-handler/src/router.rs
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use axum::{extract::{State, ConnectInfo}, routing::post, Router};
|
||||||
|
use axum_msgpack::MsgPack;
|
||||||
|
use sqlx::SqlitePool;
|
||||||
|
use tokio::sync::broadcast;
|
||||||
|
|
||||||
|
use sparse_actions::messages;
|
||||||
|
|
||||||
|
use crate::{BeaconEvent, error};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct ListenerState {
|
||||||
|
db: SqlitePool,
|
||||||
|
event_publisher: broadcast::Sender<BeaconEvent>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[axum::debug_handler]
|
||||||
|
pub async fn handle_checkin(
|
||||||
|
State(state): State<ListenerState>,
|
||||||
|
ConnectInfo(addr): ConnectInfo<SocketAddr>,
|
||||||
|
MsgPack(reg): MsgPack<messages::RegisterBeacon>,
|
||||||
|
) -> Result<MsgPack<messages::BeaconConfig>, error::Error> {
|
||||||
|
struct DbBeaconConfig {
|
||||||
|
mode: Option<String>,
|
||||||
|
regular_interval: Option<i64>,
|
||||||
|
random_min_time: Option<i64>,
|
||||||
|
random_max_time: Option<i64>,
|
||||||
|
cron_schedule: Option<String>,
|
||||||
|
cron_mode: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
use messages::{CronTimezone, RuntimeConfig as RC};
|
||||||
|
|
||||||
|
fn parse_db_config(rec: DbBeaconConfig) -> Option<RC> {
|
||||||
|
Some(match &*rec.mode? {
|
||||||
|
"single" => RC::Oneshot,
|
||||||
|
"regular" => RC::Regular { interval: rec.regular_interval? as u64 },
|
||||||
|
"random" => RC::Random {
|
||||||
|
interval_min: rec.random_min_time? as u64,
|
||||||
|
interval_max: rec.random_max_time? as u64
|
||||||
|
},
|
||||||
|
"cron" => RC::Cron {
|
||||||
|
schedule: rec.cron_schedule?,
|
||||||
|
timezone: match &*rec.cron_mode? {
|
||||||
|
"utc" => CronTimezone::Utc,
|
||||||
|
"local" => CronTimezone::Local,
|
||||||
|
_ => None?
|
||||||
|
}
|
||||||
|
} ,
|
||||||
|
_ => None?
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
tracing::info!("Beacon {} connecting from {addr}", ®.beacon_id);
|
||||||
|
|
||||||
|
let current_beacon_reg = sqlx::query_as!(
|
||||||
|
DbBeaconConfig,
|
||||||
|
r"SELECT c.mode as mode, c.regular_interval as regular_interval, c.random_min_time as random_min_time,
|
||||||
|
c.random_max_time as random_max_time, c.cron_schedule as cron_schedule, c.cron_mode as cron_mode
|
||||||
|
FROM beacon_instance i
|
||||||
|
INNER JOIN beacon_config c ON c.config_id = i.config_id
|
||||||
|
WHERE i.beacon_id = ?
|
||||||
|
UNION
|
||||||
|
SELECT c.mode as mode, c.regular_interval as regular_interval, c.random_min_time as random_min_time,
|
||||||
|
c.random_max_time as random_max_time, c.cron_schedule as cron_schedule, c.cron_mode as cron_mode
|
||||||
|
FROM beacon_instance i
|
||||||
|
INNER JOIN beacon_template t ON i.template_id = t.template_id
|
||||||
|
INNER JOIN beacon_config c ON t.config_id = c.config_id
|
||||||
|
WHERE i.beacon_id = ?"r,
|
||||||
|
reg.beacon_id,
|
||||||
|
reg.beacon_id
|
||||||
|
)
|
||||||
|
.fetch_optional(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let current_beacon_reg = match current_beacon_reg {
|
||||||
|
Some(rec) => {
|
||||||
|
parse_db_config(rec)
|
||||||
|
},
|
||||||
|
None => {
|
||||||
|
let ip = format!("{}", addr.ip());
|
||||||
|
let cwd = reg
|
||||||
|
.cwd
|
||||||
|
.to_str()
|
||||||
|
.unwrap_or("(unknown)");
|
||||||
|
sqlx::query!(
|
||||||
|
r#"INSERT INTO beacon_instance
|
||||||
|
(beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname)
|
||||||
|
VALUES
|
||||||
|
(?, ?, ?, "", ?, ?, ?, ?)"#r,
|
||||||
|
reg.beacon_id,
|
||||||
|
reg.template_id,
|
||||||
|
ip,
|
||||||
|
cwd,
|
||||||
|
reg.operating_system,
|
||||||
|
reg.userent,
|
||||||
|
reg.hostname
|
||||||
|
)
|
||||||
|
.execute(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
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
|
||||||
|
FROM beacon_template t
|
||||||
|
INNER JOIN beacon_config c ON c.config_id = t.config_id
|
||||||
|
WHERE t.template_id = ?",
|
||||||
|
reg.template_id
|
||||||
|
)
|
||||||
|
.fetch_one(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
parse_db_config(rec)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let now = chrono::Utc::now();
|
||||||
|
sqlx::query!(
|
||||||
|
r"INSERT INTO beacon_checkin (beacon_id, checkin_date) VALUES (?, ?)"r,
|
||||||
|
reg.beacon_id,
|
||||||
|
now
|
||||||
|
)
|
||||||
|
.execute(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let current_beacon_reg = current_beacon_reg
|
||||||
|
.ok_or(error::Error::Generic("could not load configuration".to_string()))?;
|
||||||
|
|
||||||
|
Ok(MsgPack(messages::BeaconConfig {
|
||||||
|
runtime_config: current_beacon_reg
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_router(db: SqlitePool, event_publisher: broadcast::Sender<BeaconEvent>) -> Router<()> {
|
||||||
|
Router::new()
|
||||||
|
.route(
|
||||||
|
"/checkin",
|
||||||
|
post(handle_checkin),
|
||||||
|
)
|
||||||
|
.route(
|
||||||
|
"/upload/:beaconid/:commandid",
|
||||||
|
post(|| async {
|
||||||
|
tracing::info!("Hello");
|
||||||
|
"hi there"
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.with_state(ListenerState { db, event_publisher })
|
||||||
|
}
|
||||||
50
sparse-server/.sqlx/query-04472775affd2b1694c05f5ab8125528bb5448e0378a7b9cf3ed58eaa2101d1e.json
generated
Normal file
50
sparse-server/.sqlx/query-04472775affd2b1694c05f5ab8125528bb5448e0378a7b9cf3ed58eaa2101d1e.json
generated
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT c.mode, c.regular_interval, c.random_min_time, c.random_max_time, c.cron_schedule, c.cron_mode\n FROM beacon_template t\n INNER JOIN beacon_config c ON c.config_id = t.config_id\n WHERE t.template_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "regular_interval",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_min_time",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_max_time",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cron_schedule",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cron_mode",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 1
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "04472775affd2b1694c05f5ab8125528bb5448e0378a7b9cf3ed58eaa2101d1e"
|
||||||
|
}
|
||||||
12
sparse-server/.sqlx/query-6e5e4ddd5c8b60c2180ca4cadf1449dc8b8b37a8887cd5b01f1ca9d0a411b897.json
generated
Normal file
12
sparse-server/.sqlx/query-6e5e4ddd5c8b60c2180ca4cadf1449dc8b8b37a8887cd5b01f1ca9d0a411b897.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT INTO beacon_checkin (beacon_id, checkin_date) VALUES (?, ?)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "6e5e4ddd5c8b60c2180ca4cadf1449dc8b8b37a8887cd5b01f1ca9d0a411b897"
|
||||||
|
}
|
||||||
68
sparse-server/.sqlx/query-75edc2bc9adda52aa7e9cd68f980db95744690cb3fc1b9cccfb3ab6f63d0ab25.json
generated
Normal file
68
sparse-server/.sqlx/query-75edc2bc9adda52aa7e9cd68f980db95744690cb3fc1b9cccfb3ab6f63d0ab25.json
generated
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname, config_id FROM beacon_instance",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "beacon_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "template_id",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "peer_ip",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "nickname",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cwd",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "operating_system",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "beacon_userent",
|
||||||
|
"ordinal": 6,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "hostname",
|
||||||
|
"ordinal": 7,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "config_id",
|
||||||
|
"ordinal": 8,
|
||||||
|
"type_info": "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 0
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "75edc2bc9adda52aa7e9cd68f980db95744690cb3fc1b9cccfb3ab6f63d0ab25"
|
||||||
|
}
|
||||||
50
sparse-server/.sqlx/query-bb07fb691f373dea848c0368d4c36e4c2b079d2d1efc0006d43d6833023101a3.json
generated
Normal file
50
sparse-server/.sqlx/query-bb07fb691f373dea848c0368d4c36e4c2b079d2d1efc0006d43d6833023101a3.json
generated
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT c.mode as mode, c.regular_interval as regular_interval, c.random_min_time as random_min_time,\n c.random_max_time as random_max_time, c.cron_schedule as cron_schedule, c.cron_mode as cron_mode\n FROM beacon_instance i\n INNER JOIN beacon_config c ON c.config_id = i.config_id\n WHERE i.beacon_id = ?\n UNION\n SELECT c.mode as mode, c.regular_interval as regular_interval, c.random_min_time as random_min_time,\n c.random_max_time as random_max_time, c.cron_schedule as cron_schedule, c.cron_mode as cron_mode\n FROM beacon_instance i\n INNER JOIN beacon_template t ON i.template_id = t.template_id\n INNER JOIN beacon_config c ON t.config_id = c.config_id\n WHERE i.beacon_id = ?",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "mode",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "regular_interval",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_min_time",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "random_max_time",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cron_schedule",
|
||||||
|
"ordinal": 4,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "cron_mode",
|
||||||
|
"ordinal": 5,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true,
|
||||||
|
true
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "bb07fb691f373dea848c0368d4c36e4c2b079d2d1efc0006d43d6833023101a3"
|
||||||
|
}
|
||||||
26
sparse-server/.sqlx/query-e7dddc194dcb297f672a9270f801ea23192e9fa53559b9fcfcffddeb3f571d15.json
generated
Normal file
26
sparse-server/.sqlx/query-e7dddc194dcb297f672a9270f801ea23192e9fa53559b9fcfcffddeb3f571d15.json
generated
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "SELECT beacon_id, category_id FROM beacon_category_assignment",
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "beacon_id",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "category_id",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 0
|
||||||
|
},
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"hash": "e7dddc194dcb297f672a9270f801ea23192e9fa53559b9fcfcffddeb3f571d15"
|
||||||
|
}
|
||||||
@ -1,36 +1,26 @@
|
|||||||
{
|
{
|
||||||
"db_name": "SQLite",
|
"db_name": "SQLite",
|
||||||
"query": "SELECT * FROM beacon_listener WHERE listener_id = ?",
|
"query": "SELECT port, domain_name, certificate, privkey FROM beacon_listener WHERE listener_id = ?",
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
"name": "listener_id",
|
"name": "port",
|
||||||
"ordinal": 0,
|
"ordinal": 0,
|
||||||
"type_info": "Integer"
|
"type_info": "Integer"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "port",
|
|
||||||
"ordinal": 1,
|
|
||||||
"type_info": "Integer"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "public_ip",
|
|
||||||
"ordinal": 2,
|
|
||||||
"type_info": "Text"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "domain_name",
|
"name": "domain_name",
|
||||||
"ordinal": 3,
|
"ordinal": 1,
|
||||||
"type_info": "Text"
|
"type_info": "Text"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "certificate",
|
"name": "certificate",
|
||||||
"ordinal": 4,
|
"ordinal": 2,
|
||||||
"type_info": "Blob"
|
"type_info": "Blob"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "privkey",
|
"name": "privkey",
|
||||||
"ordinal": 5,
|
"ordinal": 3,
|
||||||
"type_info": "Blob"
|
"type_info": "Blob"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
@ -38,13 +28,11 @@
|
|||||||
"Right": 1
|
"Right": 1
|
||||||
},
|
},
|
||||||
"nullable": [
|
"nullable": [
|
||||||
false,
|
|
||||||
false,
|
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false
|
false
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"hash": "e7dc753795b8976b14b5c4baec20d16eff715a4d2ffe93c6723bad368483fb69"
|
"hash": "f130d1b3b891a4f5d57b69bcf111230b85d7fb636517e825a42c2a59fc8b8311"
|
||||||
}
|
}
|
||||||
12
sparse-server/.sqlx/query-fa45ebf8fb26791336d5ab3701a3fc6fdf17d3eaddb0e5cff099e0396fe4dddd.json
generated
Normal file
12
sparse-server/.sqlx/query-fa45ebf8fb26791336d5ab3701a3fc6fdf17d3eaddb0e5cff099e0396fe4dddd.json
generated
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"db_name": "SQLite",
|
||||||
|
"query": "INSERT INTO beacon_instance\n (beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname)\n VALUES\n (?, ?, ?, \"\", ?, ?, ?, ?)",
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 7
|
||||||
|
},
|
||||||
|
"nullable": []
|
||||||
|
},
|
||||||
|
"hash": "fa45ebf8fb26791336d5ab3701a3fc6fdf17d3eaddb0e5cff099e0396fe4dddd"
|
||||||
|
}
|
||||||
@ -30,7 +30,7 @@ futures-util = { version = "0.3", optional = true }
|
|||||||
tracing = { version = "0.1", optional = true }
|
tracing = { version = "0.1", optional = true }
|
||||||
web-sys = { version = "0.3", features = ["WebSocket"] }
|
web-sys = { version = "0.3", features = ["WebSocket"] }
|
||||||
leptos-use = { version = "0.15", default-features = false, features = ["use_websocket", "use_interval"] }
|
leptos-use = { version = "0.15", default-features = false, features = ["use_websocket", "use_interval"] }
|
||||||
codee = "0.2"
|
codee = { version = "0.2", features = ["json_serde"] }
|
||||||
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "sqlx-sqlite"], optional = true }
|
sqlx = { version = "0.8", default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "sqlx-sqlite"], optional = true }
|
||||||
chrono = { version = "0.4", features = ["serde"] }
|
chrono = { version = "0.4", features = ["serde"] }
|
||||||
rpassword = { version = "7.3", optional = true }
|
rpassword = { version = "7.3", optional = true }
|
||||||
@ -46,6 +46,8 @@ rand = { version = "0.9", optional = true }
|
|||||||
|
|
||||||
sparse-actions = { path = "../sparse-actions", optional = true }
|
sparse-actions = { path = "../sparse-actions", optional = true }
|
||||||
sparse-handler = { path = "../sparse-handler", optional = true }
|
sparse-handler = { path = "../sparse-handler", optional = true }
|
||||||
|
serde_json = "1.0.139"
|
||||||
|
send_wrapper = "0.6.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
embed-beacons = []
|
embed-beacons = []
|
||||||
|
|||||||
@ -0,0 +1,19 @@
|
|||||||
|
DROP TABLE beacon_config_assignment;
|
||||||
|
DROP TABLE beacon_instance;
|
||||||
|
|
||||||
|
CREATE TABLE beacon_instance (
|
||||||
|
beacon_id varchar PRIMARY KEY NOT NULL,
|
||||||
|
template_id int NOT NULL,
|
||||||
|
peer_ip varchar NOT NULL,
|
||||||
|
nickname varchar NOT NULL,
|
||||||
|
|
||||||
|
cwd varchar NOT NULL,
|
||||||
|
operating_system varchar NOT NULL,
|
||||||
|
beacon_userent varchar NOT NULL,
|
||||||
|
hostname varchar NOT NULL,
|
||||||
|
|
||||||
|
config_id int,
|
||||||
|
|
||||||
|
FOREIGN KEY (template_id) REFERENCES beacon_template,
|
||||||
|
FOREIGN KEY (config_id) REFERENCES beacon_config
|
||||||
|
);
|
||||||
11
sparse-server/migrations/20250222071958_new_assignments.sql
Normal file
11
sparse-server/migrations/20250222071958_new_assignments.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
DROP TABLE beacon_category_assignment;
|
||||||
|
|
||||||
|
CREATE TABLE beacon_category_assignment (
|
||||||
|
category_id int NOT NULL,
|
||||||
|
beacon_id varchar NOT NULL,
|
||||||
|
|
||||||
|
PRIMARY KEY (category_id, beacon_id),
|
||||||
|
|
||||||
|
FOREIGN KEY (category_id) REFERENCES beacon_category,
|
||||||
|
FOREIGN KEY (beacon_id) REFERENCES beacon_instance
|
||||||
|
);
|
||||||
@ -56,6 +56,7 @@ pub fn App() -> impl IntoView {
|
|||||||
|
|
||||||
let login = ServerAction::<Login>::new();
|
let login = ServerAction::<Login>::new();
|
||||||
|
|
||||||
|
#[cfg_attr(not(feature = "hydrate"), allow(unused_variables))]
|
||||||
let (user_res, set_user_res) = signal(None::<User>);
|
let (user_res, set_user_res) = signal(None::<User>);
|
||||||
|
|
||||||
let user = Resource::new(move || login.version().get(), |_| async { me().await });
|
let user = Resource::new(move || login.version().get(), |_| async { me().await });
|
||||||
@ -77,11 +78,11 @@ pub fn App() -> impl IntoView {
|
|||||||
|
|
||||||
<Router>
|
<Router>
|
||||||
<nav>
|
<nav>
|
||||||
<h1>"Sparse control"</h1>
|
<h1>"Sparse Control"</h1>
|
||||||
<A href="/">"Home"</A>
|
<A href="/">"Home"</A>
|
||||||
{move || match user_res.get() {
|
{move || match user_res.get() {
|
||||||
Some(_) => Either::Left(view! {
|
Some(_) => Either::Left(view! {
|
||||||
<A href="/beacons">"Beacon management"</A>
|
<A href="/beacons">"Beacon Management"</A>
|
||||||
<A href="/users">"Users"</A>
|
<A href="/users">"Users"</A>
|
||||||
<a
|
<a
|
||||||
href="#"
|
href="#"
|
||||||
@ -101,18 +102,18 @@ pub fn App() -> impl IntoView {
|
|||||||
}}
|
}}
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<crate::beacons::BeaconSidebar />
|
<crate::beacons::sidebar::BeaconSidebar />
|
||||||
|
|
||||||
<Routes fallback=|| "Page not found.".into_view()>
|
<Routes fallback=|| "Page not found.".into_view()>
|
||||||
<Route path=path!("users") view=crate::users::UserView />
|
<Route path=path!("users") view=crate::users::UserView />
|
||||||
<Route path=path!("login") view=move || view! { <LoginPage login/> }/>
|
<Route path=path!("login") view=move || view! { <LoginPage login/> }/>
|
||||||
<ParentRoute path=path!("beacons") view=crate::beacons::BeaconView>
|
<ParentRoute path=path!("beacons") view=crate::beacons::BeaconView>
|
||||||
<Route path=path!("categories") view=crate::beacons::CategoriesView/>
|
<Route path=path!("categories") view=crate::beacons::categories::CategoriesView/>
|
||||||
<Route path=path!("commands") view=crate::beacons::CommandsView/>
|
<Route path=path!("commands") view=crate::beacons::commands::CommandsView/>
|
||||||
<Route path=path!("configs") view=crate::beacons::ConfigsView/>
|
<Route path=path!("configs") view=crate::beacons::configs::ConfigsView/>
|
||||||
<Route path=path!("templates") view=crate::beacons::TemplatesView/>
|
<Route path=path!("templates") view=crate::beacons::templates::TemplatesView/>
|
||||||
<Route path=path!("instances") view=crate::beacons::InstancesView/>
|
<Route path=path!("instances") view=crate::beacons::instances::InstancesView/>
|
||||||
<Route path=path!("listeners") view=crate::beacons::ListenersView/>
|
<Route path=path!("listeners") view=crate::beacons::listeners::ListenersView/>
|
||||||
<Route path=path!("") view=|| view! {
|
<Route path=path!("") view=|| view! {
|
||||||
<p>"Select a menu item on the left to get started"</p>
|
<p>"Select a menu item on the left to get started"</p>
|
||||||
}/>
|
}/>
|
||||||
|
|||||||
@ -1,25 +1,13 @@
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_router::{components::A, nested_router::Outlet};
|
use leptos_router::{components::A, nested_router::Outlet};
|
||||||
|
|
||||||
mod categories;
|
pub mod categories;
|
||||||
mod commands;
|
pub mod commands;
|
||||||
mod configs;
|
pub mod configs;
|
||||||
mod instances;
|
pub mod instances;
|
||||||
mod listeners;
|
pub mod listeners;
|
||||||
mod templates;
|
pub mod templates;
|
||||||
|
pub mod sidebar;
|
||||||
#[allow(dead_code)]
|
|
||||||
pub use categories::CategoriesView;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub use commands::CommandsView;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub use configs::ConfigsView;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub use instances::InstancesView;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub use listeners::ListenersView;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
pub use templates::TemplatesView;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BeaconResources {
|
pub struct BeaconResources {
|
||||||
@ -39,6 +27,9 @@ pub struct BeaconResources {
|
|||||||
templates: Resource<Result<Vec<templates::BeaconTemplate>, ServerFnError>>,
|
templates: Resource<Result<Vec<templates::BeaconTemplate>, ServerFnError>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// For some reason, this function "isn't used"
|
||||||
|
// See app.rs:72
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn provide_beacon_resources() {
|
pub fn provide_beacon_resources() {
|
||||||
let user = expect_context::<ReadSignal<Option<crate::users::User>>>();
|
let user = expect_context::<ReadSignal<Option<crate::users::User>>>();
|
||||||
|
|
||||||
@ -146,53 +137,3 @@ pub fn BeaconView() -> impl IntoView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SortMethod {
|
|
||||||
Listener,
|
|
||||||
Config,
|
|
||||||
Category,
|
|
||||||
Template,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::str::FromStr for SortMethod {
|
|
||||||
type Err = ();
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
match s {
|
|
||||||
"Listener" => Ok(Self::Listener),
|
|
||||||
"Config" => Ok(Self::Config),
|
|
||||||
"Category" => Ok(Self::Category),
|
|
||||||
"Template" => Ok(Self::Template),
|
|
||||||
&_ => Err(()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::string::ToString for SortMethod {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
use SortMethod as SM;
|
|
||||||
match self {
|
|
||||||
SM::Listener => "Listener",
|
|
||||||
SM::Config => "Config",
|
|
||||||
SM::Category => "Category",
|
|
||||||
SM::Template => "Template",
|
|
||||||
}
|
|
||||||
.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[component]
|
|
||||||
pub fn BeaconSidebar() -> impl IntoView {
|
|
||||||
let (sort_method, set_sort_method) = signal(SortMethod::Category);
|
|
||||||
let search_input = RwSignal::new("".to_string());
|
|
||||||
|
|
||||||
view! {
|
|
||||||
<aside class="beacons">
|
|
||||||
<div class="sort-method">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
<div class="search">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</aside>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use leptos::{either::Either, prelude::*};
|
use leptos::{either::Either, prelude::*};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use {crate::db::user, leptos::server_fn::error::NoCustomError, sqlx::SqlitePool};
|
use {crate::db::user, leptos::server_fn::error::NoCustomError};
|
||||||
|
|
||||||
use super::BeaconResources;
|
use super::BeaconResources;
|
||||||
|
|
||||||
|
|||||||
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use {
|
use {
|
||||||
crate::db::user,
|
crate::db::user,
|
||||||
leptos::server_fn::error::NoCustomError,
|
leptos::server_fn::error::NoCustomError,
|
||||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
sqlx::{sqlite::SqliteRow, FromRow, Row},
|
||||||
std::str::FromStr,
|
std::str::FromStr,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -7,9 +7,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use {
|
use {
|
||||||
crate::db::user,
|
crate::db::user,
|
||||||
leptos::server_fn::error::NoCustomError,
|
leptos::server_fn::error::NoCustomError,
|
||||||
rcgen::{generate_simple_self_signed, CertifiedKey},
|
sparse_handler::BeaconListenerMap
|
||||||
sparse_handler::BeaconListenerMap,
|
|
||||||
sqlx::SqlitePool,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::BeaconResources;
|
use super::BeaconResources;
|
||||||
@ -185,7 +183,12 @@ pub async fn start_listener(listener_id: i64) -> Result<(), ServerFnError> {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
sparse_handler::start_listener(expect_context(), listener_id, expect_context()).await?;
|
sparse_handler::start_listener(
|
||||||
|
expect_context(),
|
||||||
|
listener_id,
|
||||||
|
expect_context(),
|
||||||
|
expect_context()
|
||||||
|
).await?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
311
sparse-server/src/beacons/sidebar.rs
Normal file
311
sparse-server/src/beacons/sidebar.rs
Normal file
@ -0,0 +1,311 @@
|
|||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use leptos::prelude::*;
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
use leptos_use::{use_websocket, UseWebSocketReturn};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::beacons::BeaconResources;
|
||||||
|
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
use super::templates::BeaconTemplate;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
enum SortMethod {
|
||||||
|
Listener,
|
||||||
|
Config,
|
||||||
|
Category,
|
||||||
|
Template,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for SortMethod {
|
||||||
|
type Err = ();
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
match s {
|
||||||
|
"Listener" => Ok(Self::Listener),
|
||||||
|
"Config" => Ok(Self::Config),
|
||||||
|
"Category" => Ok(Self::Category),
|
||||||
|
"Template" => Ok(Self::Template),
|
||||||
|
&_ => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::string::ToString for SortMethod {
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
use SortMethod as SM;
|
||||||
|
match self {
|
||||||
|
SM::Listener => "Listener",
|
||||||
|
SM::Config => "Config",
|
||||||
|
SM::Category => "Category",
|
||||||
|
SM::Template => "Template",
|
||||||
|
}
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct CurrentBeaconInstance {
|
||||||
|
pub beacon_id: String,
|
||||||
|
pub ip: String,
|
||||||
|
pub nickname: String,
|
||||||
|
pub cwd: String,
|
||||||
|
pub operating_system: String,
|
||||||
|
pub userent: String,
|
||||||
|
pub hostname: String,
|
||||||
|
pub last_checkin: chrono::DateTime<chrono::Utc>,
|
||||||
|
pub config_id: Option<i64>,
|
||||||
|
pub template_id: i64,
|
||||||
|
pub category_ids: Vec<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety: the only time this comes up is on the client, which is
|
||||||
|
// not a multi-threaded environment
|
||||||
|
unsafe impl Send for CurrentBeaconInstance {}
|
||||||
|
unsafe impl Send for SidebarEvents {}
|
||||||
|
|
||||||
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
|
pub enum SidebarEvents {
|
||||||
|
BeaconList(Vec<CurrentBeaconInstance>),
|
||||||
|
NewBeacon(CurrentBeaconInstance),
|
||||||
|
Checkin(String),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn BeaconSidebar() -> impl IntoView {
|
||||||
|
let BeaconResources {
|
||||||
|
listeners,
|
||||||
|
templates,
|
||||||
|
configs,
|
||||||
|
categories,
|
||||||
|
..
|
||||||
|
} = expect_context::<BeaconResources>();
|
||||||
|
|
||||||
|
let current_beacons = RwSignal::new(None::<Vec<CurrentBeaconInstance>>);
|
||||||
|
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
let (web_socket, rebuild_websocket) = signal(use_websocket::<
|
||||||
|
(),
|
||||||
|
SidebarEvents,
|
||||||
|
codee::string::JsonSerdeCodec,
|
||||||
|
>("/api/subscribe/listener"));
|
||||||
|
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
Effect::new(move |_| {
|
||||||
|
web_socket.with(move |uwsr| {
|
||||||
|
uwsr.message.with(move |message| {
|
||||||
|
let Some(m) = message else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match m {
|
||||||
|
SidebarEvents::BeaconList(bs) => {
|
||||||
|
let mut bs = bs.to_vec();
|
||||||
|
bs.sort_by_key(|b| b.last_checkin);
|
||||||
|
current_beacons.set(Some(bs));
|
||||||
|
}
|
||||||
|
SidebarEvents::NewBeacon(b) => {
|
||||||
|
current_beacons.update(|bso| {
|
||||||
|
if let Some(ref mut bs) = bso {
|
||||||
|
bs.push(b.clone())
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
SidebarEvents::Checkin(bid) => current_beacons.update(|bs| {
|
||||||
|
let Some(ref mut bs) = bs else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if let Some(ref mut b) = bs.iter_mut().find(|b| b.beacon_id == *bid) {
|
||||||
|
b.last_checkin = chrono::Utc::now();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "hydrate")]
|
||||||
|
Effect::new(move |_| {
|
||||||
|
let user = expect_context::<ReadSignal<Option<crate::users::User>>>();
|
||||||
|
user.with(move |_| {
|
||||||
|
rebuild_websocket(use_websocket::<
|
||||||
|
(),
|
||||||
|
SidebarEvents,
|
||||||
|
codee::string::JsonSerdeCodec,
|
||||||
|
>("/api/subscribe/listener"));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
let (sort_method, set_sort_method) = signal(None::<SortMethod>);
|
||||||
|
let search_input = RwSignal::new("".to_string());
|
||||||
|
|
||||||
|
struct BeaconPartition {
|
||||||
|
title: Option<String>,
|
||||||
|
beacons: Arc<dyn Fn() -> Vec<CurrentBeaconInstance> + Send + Sync>,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Safety: BeaconPartition is only ever constructed on the client side,
|
||||||
|
// where there is only one thread
|
||||||
|
unsafe impl Send for BeaconPartition {}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "ssr"))]
|
||||||
|
let partitions = move || -> Vec<BeaconPartition> {
|
||||||
|
leptos::logging::log!(
|
||||||
|
"There are {:?} beacons",
|
||||||
|
current_beacons.read().as_ref().map(Vec::len)
|
||||||
|
);
|
||||||
|
let sm = sort_method.read();
|
||||||
|
|
||||||
|
//let Some(Ok(ref listeners)) = *listeners.read() else {
|
||||||
|
// return vec![];
|
||||||
|
//};
|
||||||
|
//let Some(Ok(ref templates)) = *templates.read() else {
|
||||||
|
// return vec![];
|
||||||
|
//};
|
||||||
|
//let Some(Ok(ref categories)) = *categories.read() else {
|
||||||
|
// return vec![];
|
||||||
|
//};
|
||||||
|
|
||||||
|
match *sm {
|
||||||
|
Some(SortMethod::Config) => {
|
||||||
|
let Some(Ok(ref configs)) = *configs.read() else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
configs
|
||||||
|
.iter()
|
||||||
|
.map(|config| {
|
||||||
|
let config = config.clone();
|
||||||
|
BeaconPartition {
|
||||||
|
title: Some(config.config_name.clone()),
|
||||||
|
beacons: Arc::new(move || {
|
||||||
|
let Some(Ok(ref templates)) = *templates.read() else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
current_beacons
|
||||||
|
.get()
|
||||||
|
.unwrap_or(vec![])
|
||||||
|
.iter()
|
||||||
|
.filter(|b| {
|
||||||
|
b.config_id.or(templates
|
||||||
|
.iter()
|
||||||
|
.find(|t| t.template_id == b.template_id)
|
||||||
|
.map(|t| t.config_id))
|
||||||
|
== Some(config.config_id)
|
||||||
|
})
|
||||||
|
.map(Clone::clone)
|
||||||
|
.collect()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
Some(SortMethod::Listener) => {
|
||||||
|
let Some(Ok(ref listeners)) = *listeners.read() else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
listeners
|
||||||
|
.iter()
|
||||||
|
.map(|listener| {
|
||||||
|
let listener = listener.clone();
|
||||||
|
BeaconPartition {
|
||||||
|
title: Some(listener.domain_name.clone()),
|
||||||
|
beacons: Arc::new(move || {
|
||||||
|
let Some(Ok(ref templates)) = *templates.read() else {
|
||||||
|
return vec![];
|
||||||
|
};
|
||||||
|
|
||||||
|
current_beacons
|
||||||
|
.get()
|
||||||
|
.unwrap_or(vec![])
|
||||||
|
.iter()
|
||||||
|
.filter(|b| {
|
||||||
|
templates
|
||||||
|
.iter()
|
||||||
|
.find(|t| t.template_id == b.template_id)
|
||||||
|
.map(|t| t.listener_id == listener.listener_id)
|
||||||
|
.unwrap_or_default()
|
||||||
|
})
|
||||||
|
.map(Clone::clone)
|
||||||
|
.collect()
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
_ => vec![BeaconPartition {
|
||||||
|
title: None,
|
||||||
|
beacons: Arc::new(move || current_beacons.get().unwrap_or(vec![]).clone()),
|
||||||
|
}],
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Safety: because this constructs nothing, it maintains the Safety
|
||||||
|
// invariants above
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
let partitions = || {
|
||||||
|
vec![BeaconPartition {
|
||||||
|
title: None,
|
||||||
|
beacons: Arc::new(move || vec![]),
|
||||||
|
}]
|
||||||
|
};
|
||||||
|
|
||||||
|
view! {
|
||||||
|
<aside class="beacons">
|
||||||
|
<div class="sort-method">
|
||||||
|
<p>"Sort beacons by:"</p>
|
||||||
|
<select
|
||||||
|
name="beacon-sort"
|
||||||
|
on:change:target=move |ev| {
|
||||||
|
set_sort_method(ev.target().value().parse().ok());
|
||||||
|
}
|
||||||
|
prop:value=move || sort_method
|
||||||
|
.get()
|
||||||
|
.as_ref()
|
||||||
|
.map(SortMethod::to_string)
|
||||||
|
.unwrap_or("".to_string())
|
||||||
|
>
|
||||||
|
<option value="">"---"</option>
|
||||||
|
<option value="Listener">"Listener"</option>
|
||||||
|
<option value="Config">"Config"</option>
|
||||||
|
<option value="Category">"Category"</option>
|
||||||
|
<option value="Template">"Template"</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="search">
|
||||||
|
<input bind:value=search_input name="beacon-search" placeholder="Search..." />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Suspense fallback=|| view! { "Loading..." }>
|
||||||
|
<div class="beacon-list">
|
||||||
|
{move || partitions()
|
||||||
|
.iter()
|
||||||
|
.map(|partition| view! {
|
||||||
|
<div class="beacon-partition">
|
||||||
|
{partition.title.as_ref().map(|title| view! {
|
||||||
|
<div class="partition-title">
|
||||||
|
{title.clone()}
|
||||||
|
</div>
|
||||||
|
})}
|
||||||
|
|
||||||
|
<For
|
||||||
|
each={
|
||||||
|
let beacons = Arc::clone(&partition.beacons);
|
||||||
|
move || (beacons)()
|
||||||
|
}
|
||||||
|
key=|b| b.beacon_id.clone()
|
||||||
|
let:beacon
|
||||||
|
>
|
||||||
|
<div>{beacon.beacon_id.clone()}</div>
|
||||||
|
</For>
|
||||||
|
</div>
|
||||||
|
})
|
||||||
|
.collect_view()}
|
||||||
|
</div>
|
||||||
|
</Suspense>
|
||||||
|
</aside>
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,7 +4,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use {
|
use {
|
||||||
crate::db::user,
|
crate::db::user,
|
||||||
leptos::server_fn::error::NoCustomError,
|
leptos::server_fn::error::NoCustomError,
|
||||||
sqlx::{sqlite::SqliteRow, FromRow, Row, SqlitePool},
|
sqlx::{sqlite::SqliteRow, FromRow, Row},
|
||||||
std::net::Ipv4Addr,
|
std::net::Ipv4Addr,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,18 +35,18 @@ impl FromRow<'_, SqliteRow> for BeaconSourceMode {
|
|||||||
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
#[cfg_attr(feature = "ssr", derive(FromRow))]
|
||||||
#[derive(Clone, Serialize, Deserialize)]
|
#[derive(Clone, Serialize, Deserialize)]
|
||||||
pub struct BeaconTemplate {
|
pub struct BeaconTemplate {
|
||||||
template_id: i64,
|
pub template_id: i64,
|
||||||
template_name: String,
|
pub template_name: String,
|
||||||
operating_system: String,
|
pub operating_system: String,
|
||||||
|
|
||||||
source_ip: String,
|
pub source_ip: String,
|
||||||
source_mac: Option<String>,
|
pub source_mac: Option<String>,
|
||||||
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
#[cfg_attr(feature = "ssr", sqlx(flatten))]
|
||||||
source_mode: BeaconSourceMode,
|
pub source_mode: BeaconSourceMode,
|
||||||
|
|
||||||
config_id: i64,
|
pub config_id: i64,
|
||||||
listener_id: i64,
|
pub listener_id: i64,
|
||||||
default_category: Option<i64>,
|
pub default_category: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_if::cfg_if! {
|
cfg_if::cfg_if! {
|
||||||
@ -113,12 +113,12 @@ pub async fn add_template(
|
|||||||
.fetch_one(&db)
|
.fetch_one(&db)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
use rcgen::{Certificate, CertificateParams, KeyPair};
|
use rcgen::{CertificateParams, KeyPair};
|
||||||
|
|
||||||
let keypair = KeyPair::from_der_and_sign_algo(
|
let keypair = KeyPair::from_der_and_sign_algo(
|
||||||
match &rustls_pki_types::PrivateKeyDer::try_from(&*listener.privkey) {
|
match &rustls_pki_types::PrivateKeyDer::try_from(&*listener.privkey) {
|
||||||
Ok(pk) => pk,
|
Ok(pk) => pk,
|
||||||
Err(e) => {
|
Err(_) => {
|
||||||
srverr!("Could not parse private key: {e}");
|
srverr!("Could not parse private key: {e}");
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use axum_extra::extract::cookie::CookieJar;
|
||||||
use leptos::{prelude::*, server_fn::error::NoCustomError};
|
use leptos::{prelude::*, server_fn::error::NoCustomError};
|
||||||
use leptos_axum::{extract, ResponseOptions};
|
use leptos_axum::{extract, ResponseOptions};
|
||||||
use pbkdf2::{
|
use pbkdf2::{
|
||||||
@ -196,15 +197,10 @@ pub async fn destroy_auth_session() -> Result<(), ServerFnError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_auth_session() -> Result<Option<User>, ServerFnError> {
|
pub async fn get_auth_session_inner(
|
||||||
use axum_extra::extract::cookie::CookieJar;
|
db: SqlitePool,
|
||||||
|
jar: CookieJar
|
||||||
println!("In get auth session");
|
) -> Result<Option<User>, crate::error::Error> {
|
||||||
let owner = leptos::prelude::Owner::current().unwrap();
|
|
||||||
|
|
||||||
let db = crate::db::get_db()?;
|
|
||||||
let jar = extract::<CookieJar>().await?;
|
|
||||||
|
|
||||||
let Some(cookie) = jar.get(SESSION_ID_KEY) else {
|
let Some(cookie) = jar.get(SESSION_ID_KEY) else {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
};
|
};
|
||||||
@ -252,3 +248,12 @@ pub async fn get_auth_session() -> Result<Option<User>, ServerFnError> {
|
|||||||
|
|
||||||
Ok(user)
|
Ok(user)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn get_auth_session() -> Result<Option<User>, ServerFnError> {
|
||||||
|
let db = crate::db::get_db()?;
|
||||||
|
let jar = extract::<CookieJar>().await?;
|
||||||
|
|
||||||
|
get_auth_session_inner(db, jar)
|
||||||
|
.await
|
||||||
|
.map_err(Into::into)
|
||||||
|
}
|
||||||
|
|||||||
@ -10,7 +10,10 @@ pub enum Error {
|
|||||||
Pbkdf2(pbkdf2::password_hash::errors::Error),
|
Pbkdf2(pbkdf2::password_hash::errors::Error),
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
Io(std::io::Error),
|
Io(std::io::Error),
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
Axum(axum::Error),
|
||||||
AddrParse(std::net::AddrParseError),
|
AddrParse(std::net::AddrParseError),
|
||||||
|
Json(serde_json::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Display for Error {
|
impl std::fmt::Display for Error {
|
||||||
@ -41,6 +44,13 @@ impl std::fmt::Display for Error {
|
|||||||
Error::AddrParse(err) => {
|
Error::AddrParse(err) => {
|
||||||
write!(f, "ip address parse error: {err:?}")
|
write!(f, "ip address parse error: {err:?}")
|
||||||
}
|
}
|
||||||
|
Error::Json(err) => {
|
||||||
|
write!(f, "json encode/decode error: {err:?}")
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
Error::Axum(err) => {
|
||||||
|
write!(f, "axum error: {err:?}")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -55,6 +65,9 @@ impl std::error::Error for Error {
|
|||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
Error::Io(err) => Some(err),
|
Error::Io(err) => Some(err),
|
||||||
Error::AddrParse(err) => Some(err),
|
Error::AddrParse(err) => Some(err),
|
||||||
|
Error::Json(err) => Some(err),
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
Error::Axum(err) => Some(err),
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -112,3 +125,16 @@ impl From<std::net::AddrParseError> for Error {
|
|||||||
Self::AddrParse(err)
|
Self::AddrParse(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<serde_json::Error> for Error {
|
||||||
|
fn from(err: serde_json::Error) -> Self {
|
||||||
|
Self::Json(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "ssr")]
|
||||||
|
impl From<axum::Error> for Error {
|
||||||
|
fn from(err: axum::Error) -> Self {
|
||||||
|
Self::Axum(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -21,7 +21,7 @@ async fn main() -> anyhow::Result<std::process::ExitCode> {
|
|||||||
tracing_subscriber::registry()
|
tracing_subscriber::registry()
|
||||||
.with(
|
.with(
|
||||||
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
|
||||||
format!("{}=debug,sparse_handler=debug,tower_http=trace", env!("CARGO_CRATE_NAME")).into()
|
format!("{}=debug,sparse_handler=debug", env!("CARGO_CRATE_NAME")).into()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.with(tracing_subscriber::fmt::layer())
|
.with(tracing_subscriber::fmt::layer())
|
||||||
|
|||||||
1
sparse-server/src/socket.rs
Normal file
1
sparse-server/src/socket.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
||||||
@ -2,7 +2,7 @@ use chrono::{offset::Utc, DateTime};
|
|||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[cfg(feature = "ssr")]
|
#[cfg(feature = "ssr")]
|
||||||
use {crate::db::user, leptos::server_fn::error::NoCustomError, sqlx::SqlitePool};
|
use {crate::db::user, leptos::server_fn::error::NoCustomError};
|
||||||
|
|
||||||
pub fn format_delta(time: chrono::TimeDelta) -> String {
|
pub fn format_delta(time: chrono::TimeDelta) -> String {
|
||||||
let seconds = time.num_seconds();
|
let seconds = time.num_seconds();
|
||||||
@ -98,9 +98,9 @@ async fn reset_password(user_id: i64, password: String) -> Result<(), ServerFnEr
|
|||||||
pub fn RenderUser(refresh_user_list: Action<(), ()>, user: PubUser) -> impl IntoView {
|
pub fn RenderUser(refresh_user_list: Action<(), ()>, user: PubUser) -> impl IntoView {
|
||||||
use leptos_use::{use_interval, UseIntervalReturn};
|
use leptos_use::{use_interval, UseIntervalReturn};
|
||||||
|
|
||||||
#[cfg_attr(feature = "ssr", allow(unused_variables))]
|
#[cfg(feature = "hydrate")]
|
||||||
let UseIntervalReturn { counter, .. } = use_interval(1000);
|
let UseIntervalReturn { counter, .. } = use_interval(1000);
|
||||||
#[cfg_attr(feature = "ssr", allow(unused_variables))]
|
#[cfg_attr(not(feature = "hydrate"), allow(unused_variables))]
|
||||||
let (time_ago, set_time_ago) = signal(
|
let (time_ago, set_time_ago) = signal(
|
||||||
user.last_active
|
user.last_active
|
||||||
.map(|active| format_delta(Utc::now() - active)),
|
.map(|active| format_delta(Utc::now() - active)),
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
use std::{net::SocketAddrV4, process::ExitCode};
|
use std::{net::SocketAddrV4, process::ExitCode};
|
||||||
|
|
||||||
use axum::{
|
use axum::{
|
||||||
extract::{FromRef, Path, Query, State},
|
extract::{ws, FromRef, Path, Query, State},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
routing::{get, post},
|
routing::{get, post},
|
||||||
Router,
|
Router,
|
||||||
};
|
};
|
||||||
|
use axum_extra::extract::cookie::CookieJar;
|
||||||
use leptos::prelude::*;
|
use leptos::prelude::*;
|
||||||
use leptos_axum::{generate_route_list, LeptosRoutes};
|
use leptos_axum::{generate_route_list, LeptosRoutes};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
@ -94,10 +95,12 @@ pub async fn get_beacon(btype: &str) -> Result<Vec<u8>, crate::error::Error> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(FromRef, Clone, Debug)]
|
#[derive(FromRef, Clone)]
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
leptos_options: leptos::config::LeptosOptions,
|
leptos_options: leptos::config::LeptosOptions,
|
||||||
|
beacon_listeners: sparse_handler::BeaconListenerMap,
|
||||||
|
beacon_event_broadcast: tokio::sync::broadcast::Sender<sparse_handler::BeaconEvent>,
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_parameters_bytes(
|
async fn get_parameters_bytes(
|
||||||
@ -237,7 +240,10 @@ pub async fn download_beacon(
|
|||||||
State(db): State<AppState>,
|
State(db): State<AppState>,
|
||||||
Query(beacon_params): Query<BeaconDownloadParams>,
|
Query(beacon_params): Query<BeaconDownloadParams>,
|
||||||
) -> Result<impl IntoResponse, crate::error::Error> {
|
) -> Result<impl IntoResponse, crate::error::Error> {
|
||||||
let (parameters_bytes, operating_system) = get_parameters_bytes(template_id, db.db).await?;
|
use rand::{rngs::OsRng, TryRngCore};
|
||||||
|
use sparse_actions::payload_types::{Parameters_t, XOR_KEY};
|
||||||
|
|
||||||
|
let (mut parameters_bytes, operating_system) = get_parameters_bytes(template_id, db.db).await?;
|
||||||
|
|
||||||
let binary = if beacon_params.use_svc.unwrap_or_default() {
|
let binary = if beacon_params.use_svc.unwrap_or_default() {
|
||||||
tracing::debug!("Downloading windows service");
|
tracing::debug!("Downloading windows service");
|
||||||
@ -252,6 +258,22 @@ pub async fn download_beacon(
|
|||||||
|
|
||||||
let installer_bytes = get_beacon(&binary).await?;
|
let installer_bytes = get_beacon(&binary).await?;
|
||||||
|
|
||||||
|
let parameters: &mut Parameters_t =
|
||||||
|
unsafe { std::mem::transmute(parameters_bytes.as_mut_ptr()) };
|
||||||
|
let mut identifier = [0u8; 32];
|
||||||
|
OsRng
|
||||||
|
.try_fill_bytes(&mut identifier)
|
||||||
|
.expect("Could not generate beacon identifier");
|
||||||
|
|
||||||
|
let hex_ident = hex::encode(&identifier)
|
||||||
|
.as_bytes()
|
||||||
|
.iter()
|
||||||
|
.map(|b| b ^ (XOR_KEY as u8))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
parameters
|
||||||
|
.beacon_identifier
|
||||||
|
.copy_from_slice(&hex_ident);
|
||||||
|
|
||||||
use axum::http::header;
|
use axum::http::header;
|
||||||
|
|
||||||
Ok((
|
Ok((
|
||||||
@ -301,6 +323,118 @@ pub async fn download_beacon_installer(
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn subscribe_to_listener_events(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
cookie_jar: CookieJar,
|
||||||
|
ws: ws::WebSocketUpgrade
|
||||||
|
) -> axum::response::Response {
|
||||||
|
let user = match crate::db::user::get_auth_session_inner(
|
||||||
|
state.db.clone(),
|
||||||
|
cookie_jar
|
||||||
|
).await {
|
||||||
|
Ok(u) => u,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!("Could not load user session: {e:?}");
|
||||||
|
return axum::http::StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if user.is_none() {
|
||||||
|
return axum::http::StatusCode::UNAUTHORIZED.into_response();
|
||||||
|
}
|
||||||
|
|
||||||
|
ws
|
||||||
|
.on_upgrade(move |socket: ws::WebSocket| async move {
|
||||||
|
if let Err(e) = handle_listener_events(socket, state).await {
|
||||||
|
tracing::warn!("Encountered error when handling event subscriber: {e}");
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.into_response()
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn handle_listener_events(
|
||||||
|
mut socket: ws::WebSocket,
|
||||||
|
state: AppState,
|
||||||
|
) -> Result<(), crate::error::Error> {
|
||||||
|
use sqlx::{sqlite::SqliteRow, Row};
|
||||||
|
|
||||||
|
use crate::beacons::sidebar::{CurrentBeaconInstance, SidebarEvents};
|
||||||
|
|
||||||
|
{
|
||||||
|
let beacons = sqlx::query!(
|
||||||
|
"SELECT beacon_id, template_id, peer_ip, nickname, cwd, operating_system, beacon_userent, hostname, config_id FROM beacon_instance"
|
||||||
|
)
|
||||||
|
.fetch_all(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
struct CheckinResult {
|
||||||
|
beacon_id: String,
|
||||||
|
checkin_date: chrono::DateTime<chrono::Utc>
|
||||||
|
}
|
||||||
|
|
||||||
|
impl sqlx::FromRow<'_, SqliteRow> for CheckinResult {
|
||||||
|
fn from_row(row: &SqliteRow) -> sqlx::Result<Self> {
|
||||||
|
Ok(CheckinResult {
|
||||||
|
beacon_id: row.get("beacon_id"),
|
||||||
|
checkin_date: row.get("checkin_date")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let last_checkin: Vec<CheckinResult> = sqlx::query_as(
|
||||||
|
"SELECT beacon_id, MAX(checkin_date) as checkin_date FROM beacon_checkin
|
||||||
|
GROUP BY beacon_id"
|
||||||
|
)
|
||||||
|
.fetch_all(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let category_ids = sqlx::query!(
|
||||||
|
"SELECT beacon_id, category_id FROM beacon_category_assignment"
|
||||||
|
)
|
||||||
|
.fetch_all(&state.db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let beacons = SidebarEvents::BeaconList(
|
||||||
|
beacons
|
||||||
|
.into_iter()
|
||||||
|
.map(|b| CurrentBeaconInstance {
|
||||||
|
beacon_id: b.beacon_id.clone(),
|
||||||
|
template_id: b.template_id,
|
||||||
|
ip: b.peer_ip,
|
||||||
|
nickname: b.nickname,
|
||||||
|
cwd: b.cwd,
|
||||||
|
operating_system: b.operating_system,
|
||||||
|
userent: b.beacon_userent,
|
||||||
|
hostname: b.hostname,
|
||||||
|
config_id: b.config_id,
|
||||||
|
last_checkin: last_checkin
|
||||||
|
.iter()
|
||||||
|
.find(|ch| ch.beacon_id == b.beacon_id)
|
||||||
|
.clone()
|
||||||
|
.map(|ch| ch.checkin_date)
|
||||||
|
.unwrap_or_else(|| chrono::Utc::now()),
|
||||||
|
category_ids: category_ids
|
||||||
|
.iter()
|
||||||
|
.filter(|cat| cat.beacon_id == b.beacon_id)
|
||||||
|
.map(|cat| cat.category_id)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
);
|
||||||
|
|
||||||
|
let json = serde_json::to_string(&beacons)?;
|
||||||
|
|
||||||
|
socket.send(ws::Message::Text(json.into())).await?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut event_receiver = state.beacon_event_broadcast.subscribe();
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let event = event_receiver.recv().await;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn serve_web(
|
pub async fn serve_web(
|
||||||
management_address: SocketAddrV4,
|
management_address: SocketAddrV4,
|
||||||
db: SqlitePool,
|
db: SqlitePool,
|
||||||
@ -308,6 +442,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_listeners = sparse_handler::BeaconListenerMap::default();
|
let beacon_listeners = sparse_handler::BeaconListenerMap::default();
|
||||||
|
|
||||||
let compression_layer = tower_http::compression::CompressionLayer::new()
|
let compression_layer = tower_http::compression::CompressionLayer::new()
|
||||||
@ -316,11 +451,17 @@ pub async fn serve_web(
|
|||||||
.br(true)
|
.br(true)
|
||||||
.zstd(true);
|
.zstd(true);
|
||||||
|
|
||||||
sparse_handler::start_all_listeners(beacon_listeners.clone(), db.clone()).await?;
|
sparse_handler::start_all_listeners(
|
||||||
|
beacon_listeners.clone(),
|
||||||
|
db.clone(),
|
||||||
|
beacon_event_broadcast.clone()
|
||||||
|
).await?;
|
||||||
|
|
||||||
let state = AppState {
|
let state = AppState {
|
||||||
leptos_options: leptos_options.clone(),
|
leptos_options: leptos_options.clone(),
|
||||||
db: db.clone(),
|
db: db.clone(),
|
||||||
|
beacon_listeners: beacon_listeners.clone(),
|
||||||
|
beacon_event_broadcast: beacon_event_broadcast.clone()
|
||||||
};
|
};
|
||||||
|
|
||||||
let app = Router::new()
|
let app = Router::new()
|
||||||
@ -329,6 +470,10 @@ pub async fn serve_web(
|
|||||||
get(download_beacon_installer),
|
get(download_beacon_installer),
|
||||||
)
|
)
|
||||||
.route("/binaries/beacon/:template_id", get(download_beacon))
|
.route("/binaries/beacon/:template_id", get(download_beacon))
|
||||||
|
.route(
|
||||||
|
"/api/subscribe/listener",
|
||||||
|
axum::routing::any(subscribe_to_listener_events)
|
||||||
|
)
|
||||||
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
.route("/api/*fn_name", post(leptos_axum::handle_server_fns))
|
||||||
.leptos_routes_with_context(
|
.leptos_routes_with_context(
|
||||||
&state,
|
&state,
|
||||||
@ -336,6 +481,7 @@ pub async fn serve_web(
|
|||||||
move || {
|
move || {
|
||||||
provide_context(beacon_listeners.clone());
|
provide_context(beacon_listeners.clone());
|
||||||
provide_context(db.clone());
|
provide_context(db.clone());
|
||||||
|
provide_context(beacon_event_broadcast.clone());
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
let leptos_options = leptos_options.clone();
|
let leptos_options = leptos_options.clone();
|
||||||
|
|||||||
@ -4,6 +4,7 @@
|
|||||||
@use 'beacons/templates';
|
@use 'beacons/templates';
|
||||||
@use 'beacons/instances';
|
@use 'beacons/instances';
|
||||||
@use 'beacons/commands';
|
@use 'beacons/commands';
|
||||||
|
@use 'beacons/sidebar';
|
||||||
|
|
||||||
main.beacons {
|
main.beacons {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
6
sparse-server/style/beacons/_sidebar.scss
Normal file
6
sparse-server/style/beacons/_sidebar.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
aside.beacons {
|
||||||
|
grid-area: beacons;
|
||||||
|
background-color: #11111c;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
@ -22,6 +22,8 @@ pub struct LinuxAdapter;
|
|||||||
impl BeaconAdapter for LinuxAdapter {
|
impl BeaconAdapter for LinuxAdapter {
|
||||||
type Error = LinuxAdapterError;
|
type Error = LinuxAdapterError;
|
||||||
|
|
||||||
|
const OPERATING_SYSTEM: &'static str = "Linux";
|
||||||
|
|
||||||
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8> {
|
fn interface_name_from_interface(interface: &BeaconInterface) -> Vec<u8> {
|
||||||
interface.name.clone()
|
interface.name.clone()
|
||||||
}
|
}
|
||||||
@ -88,4 +90,33 @@ impl BeaconAdapter for LinuxAdapter {
|
|||||||
.collect(),
|
.collect(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn get_username(&self) -> Result<String, error::BeaconError<Self::Error>> {
|
||||||
|
let passwd = tokio::fs::read_to_string("/etc/passwd").await?;
|
||||||
|
let uid = unsafe { libc::getuid() };
|
||||||
|
|
||||||
|
Ok(passwd
|
||||||
|
.split("\n")
|
||||||
|
.find_map(|row| -> Option<String> {
|
||||||
|
let mut entries = row.split(":");
|
||||||
|
|
||||||
|
let name = entries.next()?;
|
||||||
|
entries.next()?;
|
||||||
|
let euid = entries.next()?.parse::<u32>().ok()?;
|
||||||
|
|
||||||
|
if euid == uid {
|
||||||
|
Some(name.to_string())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.unwrap_or("(unknown)".to_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn get_hostname(&self) -> Result<String, error::BeaconError<Self::Error>> {
|
||||||
|
Ok(tokio::fs::read_to_string("/etc/hostname")
|
||||||
|
.await?
|
||||||
|
.trim()
|
||||||
|
.to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user