From 6e31b95417b9ea6cbf34a7e9f76dae56a66b568b Mon Sep 17 00:00:00 2001 From: JMARyA Date: Wed, 30 Apr 2025 16:28:48 +0200 Subject: [PATCH 01/13] =?UTF-8?q?=E2=9C=A8=20client=20server=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + Cargo.toml | 1 + src/api.rs | 40 ++++++++++++++++++++++++++++++++++++++++ src/herd_core/mqtt.rs | 13 ++++++++++++- src/herd_core/route.rs | 2 ++ src/sheepd_core/mqtt.rs | 39 +++++++++++++++++++++++++++++++++------ 6 files changed, 89 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index dba945c..5820f28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3596,6 +3596,7 @@ dependencies = [ "toml 0.8.22", "tracing", "tracing-subscriber", + "ulid", "ureq", ] diff --git a/Cargo.toml b/Cargo.toml index bcc8e0b..39511ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,3 +36,4 @@ ureq = { version = "3.0.11", features = ["json"] } rumqttc = { version = "0.24.0", features = ["url", "websocket"] } sage = { git = "https://git.hydrar.de/jmarya/sage" } dashmap = "6.1.0" +ulid = { version = "1.2.1", features = ["serde"] } diff --git a/src/api.rs b/src/api.rs index 32e83f7..773401f 100644 --- a/src/api.rs +++ b/src/api.rs @@ -105,3 +105,43 @@ impl Result { Self { ok: 0 } } } + +#[derive(Debug, Serialize, Deserialize)] +pub struct ClientAction { + pub id: ulid::Ulid, + pub action: ClientActions, +} + +impl ClientAction { + pub fn new(action: ClientActions) -> Self { + Self { + id: ulid::Ulid::new(), + action, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum ClientActions { + OSQuery(String), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ServerResponse { + pub id: ulid::Ulid, + pub response: ServerResponses, +} + +impl ServerResponse { + pub fn of(client: &ClientAction, resp: ServerResponses) -> Self { + Self { + id: client.id, + response: resp, + } + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub enum ServerResponses { + OSQuery(String), +} diff --git a/src/herd_core/mqtt.rs b/src/herd_core/mqtt.rs index 550bac0..bd0276f 100644 --- a/src/herd_core/mqtt.rs +++ b/src/herd_core/mqtt.rs @@ -1,4 +1,7 @@ +use std::sync::Arc; + use crate::Machine; +use crate::api::{ClientAction, ClientActions, ServerResponse}; use owl::prelude::*; use owl::{Serialize, get, query}; use rumqttc::AsyncClient; @@ -17,12 +20,16 @@ pub async fn handle_mqtt(topic: String, data: Vec) { .unwrap(); // TODO : check for recency - println!("got raw: {}", String::from_utf8(dec.payload).unwrap()); match cat { "online" => { log::info!("Device {client} reported ONLINE"); } + "respond" => { + let resp: ServerResponse = serde_json::from_slice(&dec.payload).unwrap(); + + log::info!("Got response {:?}", resp); + } _ => {} } } @@ -53,6 +60,10 @@ pub async fn listen_to_device(client: &AsyncClient, machine_id: &str) { .subscribe(format!("{machine_id}/online"), rumqttc::QoS::AtMostOnce) .await .unwrap(); + client + .subscribe(format!("{machine_id}/respond"), rumqttc::QoS::AtMostOnce) + .await + .unwrap(); } /// Subscibe to incoming messages from all registered machines diff --git a/src/herd_core/route.rs b/src/herd_core/route.rs index b9777d1..60bd1c6 100644 --- a/src/herd_core/route.rs +++ b/src/herd_core/route.rs @@ -44,6 +44,8 @@ pub async fn join_device( let machine = Machine::from_join_param(payload); let new_token = machine.token.clone(); + // TODO : add device listener instantly + save!(machine); let i = crate::IDENTITY.get().unwrap(); diff --git a/src/sheepd_core/mqtt.rs b/src/sheepd_core/mqtt.rs index dea12e6..188db87 100644 --- a/src/sheepd_core/mqtt.rs +++ b/src/sheepd_core/mqtt.rs @@ -1,7 +1,11 @@ +use std::{os, process::Stdio}; + use owl::Serialize; use rumqttc::AsyncClient; use sage::PersonaIdentity; +use crate::api::{ClientAction, ServerResponse, ServerResponses}; + // Client MQTT pub async fn handle_mqtt(topic: String, data: Vec) { //println!("got real raw: {}", String::from_utf8_lossy(&data)); @@ -11,15 +15,38 @@ pub async fn handle_mqtt(topic: String, data: Vec) { ); let pk = pk.sign_key().unwrap(); let payload = crate::IDENTITY.get().unwrap().decrypt(&data, &pk).unwrap(); - println!( - "got payload {}", - String::from_utf8(payload.payload).unwrap() - ); + + let action: ClientAction = serde_json::from_slice(&payload.payload).unwrap(); + log::info!("Got action {action:?}"); + + match &action.action { + crate::api::ClientActions::OSQuery(query) => { + // TODO : run osquery + log::info!("Doing osquery with {query}"); + let res = osquery(&query); + send_back( + crate::MQTT.get().unwrap(), + "respond", + ServerResponse::of(&action, ServerResponses::OSQuery(res)), + ) + .await; + } + } +} + +pub fn osquery(query: &str) -> String { + let cmd = std::process::Command::new("osqueryi") + .arg("--csv") + .arg(query) + .stdout(Stdio::piped()) + .output() + .unwrap(); + String::from_utf8(cmd.stdout).unwrap() } /// Send something back to the server on `topic` -pub async fn send_back(client: &AsyncClient, topic: &str, request: T) { - let data = serde_json::to_string(&request).unwrap(); +pub async fn send_back(client: &AsyncClient, topic: &str, data: T) { + let data = serde_json::to_string(&data).unwrap(); let pk = crate::AGENT.get().unwrap(); let pk = (pk.server_age.clone(), String::new()); From 46cf2f457282e0e6e5b3b8b54895917b1bbcd4d9 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Wed, 30 Apr 2025 16:51:49 +0200 Subject: [PATCH 02/13] =?UTF-8?q?=E2=9C=A8=20online?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 1 + Cargo.toml | 1 + src/herd.rs | 5 +++++ src/herd_core/mqtt.rs | 17 ++++++++++++++++- src/sheepd_core/mqtt.rs | 2 +- 5 files changed, 24 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5820f28..37a67f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3582,6 +3582,7 @@ dependencies = [ "axum", "axum-client-ip", "based", + "chrono", "dashmap", "hex", "http2", diff --git a/Cargo.toml b/Cargo.toml index 39511ff..9b0f66c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,3 +37,4 @@ rumqttc = { version = "0.24.0", features = ["url", "websocket"] } sage = { git = "https://git.hydrar.de/jmarya/sage" } dashmap = "6.1.0" ulid = { version = "1.2.1", features = ["serde"] } +chrono = "0.4.41" diff --git a/src/herd.rs b/src/herd.rs index 420a16d..e2891cb 100644 --- a/src/herd.rs +++ b/src/herd.rs @@ -4,6 +4,7 @@ use axum::{ }; use axum_client_ip::ClientIpSource; use based::auth::User; +use dashmap::DashMap; use owl::{prelude::*, set_global_db}; use rand::RngCore; use std::{net::SocketAddr, path::PathBuf}; @@ -21,6 +22,8 @@ use tokio::sync::OnceCell; pub static IDENTITY: OnceCell = OnceCell::const_new(); pub static CONFIG: OnceCell = OnceCell::const_new(); +pub static ONLINE: OnceCell>> = OnceCell::const_new(); + fn generate_token() -> String { let mut rng = rand::rng(); let mut token = vec![0u8; 32]; @@ -44,6 +47,8 @@ async fn main() { let config = Config::default(); let _ = crate::CONFIG.set(config); + crate::ONLINE.set(DashMap::new()).unwrap(); + let db = Database::filesystem("./herd/db"); set_global_db!(db); diff --git a/src/herd_core/mqtt.rs b/src/herd_core/mqtt.rs index bd0276f..2b6323e 100644 --- a/src/herd_core/mqtt.rs +++ b/src/herd_core/mqtt.rs @@ -7,6 +7,11 @@ use owl::{Serialize, get, query}; use rumqttc::AsyncClient; use sage::PersonaIdentity; +fn is_within_80_seconds(time: chrono::DateTime) -> bool { + let now = chrono::Utc::now(); + now.signed_duration_since(time).num_seconds() <= 80 +} + /// Handle herd MQTT pub async fn handle_mqtt(topic: String, data: Vec) { log::info!("Received client request from {topic}"); @@ -23,7 +28,17 @@ pub async fn handle_mqtt(topic: String, data: Vec) { match cat { "online" => { - log::info!("Device {client} reported ONLINE"); + if let Some(online) = crate::ONLINE.get().unwrap().get(client) { + if !is_within_80_seconds(*online) { + log::info!("Device {client} came back ONLINE"); + } + } else { + log::info!("Device {client} went ONLINE"); + } + crate::ONLINE + .get() + .unwrap() + .insert(client.to_string(), chrono::Utc::now()); } "respond" => { let resp: ServerResponse = serde_json::from_slice(&dec.payload).unwrap(); diff --git a/src/sheepd_core/mqtt.rs b/src/sheepd_core/mqtt.rs index 188db87..c9b5ce6 100644 --- a/src/sheepd_core/mqtt.rs +++ b/src/sheepd_core/mqtt.rs @@ -62,7 +62,7 @@ pub async fn send_back(client: &AsyncClient, topic: &str, data: T) .encrypt(data.as_bytes(), &rec); let topic = format!("{machine_id}/{topic}"); - log::info!("Publish to {machine_id}{topic}"); + log::info!("Publish to {topic}"); client .publish(topic, rumqttc::QoS::AtMostOnce, true, payload) .await From b010027549713f24344cc45467900c4740f7b6f0 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Fri, 2 May 2025 12:53:28 +0200 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=A8=20cli=20+=20user=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 204 +++++++++++++++++++++++++++++++++++++- Cargo.toml | 11 ++ src/api.rs | 65 ++++++++++-- src/herd.rs | 6 +- src/herd_core/mqtt.rs | 2 +- src/herd_core/route.rs | 54 ++++++++-- src/lib.rs | 2 + src/sheepctl.rs | 18 ++++ src/sheepctl_core/args.rs | 51 ++++++++++ src/sheepctl_core/cmd.rs | 105 ++++++++++++++++++++ src/sheepctl_core/mod.rs | 2 + src/sheepd_core/cmd.rs | 9 +- 12 files changed, 504 insertions(+), 25 deletions(-) create mode 100644 src/lib.rs create mode 100644 src/sheepctl.rs create mode 100644 src/sheepctl_core/args.rs create mode 100644 src/sheepctl_core/cmd.rs create mode 100644 src/sheepctl_core/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 37a67f5..6fa03a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -397,6 +397,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "axum-extra" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45bf463831f5131b7d3c756525b305d40f1185b688565648a92e1392ca35713d" +dependencies = [ + "axum", + "axum-core", + "bytes", + "futures-util", + "headers", + "http 1.3.1", + "http-body 1.0.1", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "serde", + "tower", + "tower-layer", + "tower-service", +] + [[package]] name = "axum-macros" version = "0.5.0" @@ -831,6 +854,31 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crossterm" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" +dependencies = [ + "bitflags 1.3.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", + "parking_lot", + "signal-hook", + "signal-hook-mio", + "winapi", +] + +[[package]] +name = "crossterm_winapi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" +dependencies = [ + "winapi", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -968,6 +1016,27 @@ dependencies = [ "subtle", ] +[[package]] +name = "directories" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", +] + [[package]] name = "displaydoc" version = "0.2.5" @@ -994,6 +1063,12 @@ version = "0.15.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b" +[[package]] +name = "dyn-clone" +version = "1.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c7a8fb8a9fbf66c1f703fe16184d10ca0ee9d23be5b4436400408ba54a95005" + [[package]] name = "either" version = "1.15.0" @@ -1344,6 +1419,24 @@ dependencies = [ "slab", ] +[[package]] +name = "fuzzy-matcher" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54614a3312934d066701a80f20f15fa3b56d67ac7722b39eea5b4c9dd1d66c94" +dependencies = [ + "thread_local", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generator" version = "0.7.5" @@ -1496,6 +1589,30 @@ dependencies = [ "hashbrown 0.15.2", ] +[[package]] +name = "headers" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "322106e6bd0cba2d5ead589ddb8150a13d7c4217cf80d7c4f682ca994ccc6aa9" +dependencies = [ + "base64 0.21.7", + "bytes", + "headers-core", + "http 1.3.1", + "httpdate", + "mime", + "sha1", +] + +[[package]] +name = "headers-core" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" +dependencies = [ + "http 1.3.1", +] + [[package]] name = "heck" version = "0.5.0" @@ -1967,6 +2084,23 @@ dependencies = [ "generic-array", ] +[[package]] +name = "inquire" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a" +dependencies = [ + "bitflags 2.9.0", + "crossterm", + "dyn-clone", + "fuzzy-matcher", + "fxhash", + "newline-converter", + "once_cell", + "unicode-segmentation", + "unicode-width", +] + [[package]] name = "intl-memoizer" version = "0.5.2" @@ -2232,6 +2366,18 @@ dependencies = [ "adler2", ] +[[package]] +name = "mio" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.48.0", +] + [[package]] name = "mio" version = "1.0.3" @@ -2279,6 +2425,15 @@ dependencies = [ "tempfile", ] +[[package]] +name = "newline-converter" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "nom" version = "7.1.3" @@ -2433,6 +2588,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + [[package]] name = "overload" version = "0.1.1" @@ -2854,6 +3015,17 @@ dependencies = [ "bitflags 2.9.0", ] +[[package]] +name = "redox_users" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +dependencies = [ + "getrandom 0.2.16", + "libredox", + "thiserror 2.0.12", +] + [[package]] name = "ref-cast" version = "1.0.24" @@ -3581,11 +3753,14 @@ dependencies = [ "argh", "axum", "axum-client-ip", + "axum-extra", "based", "chrono", "dashmap", + "directories", "hex", "http2", + "inquire", "log", "owl", "rand 0.9.1", @@ -3607,6 +3782,27 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio 0.8.11", + "signal-hook", +] + [[package]] name = "signal-hook-registry" version = "1.4.5" @@ -4116,7 +4312,7 @@ dependencies = [ "backtrace", "bytes", "libc", - "mio", + "mio 1.0.3", "parking_lot", "pin-project-lite", "signal-hook-registry", @@ -4461,6 +4657,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + [[package]] name = "unicode-xid" version = "0.2.6" diff --git a/Cargo.toml b/Cargo.toml index 9b0f66c..6be1032 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,18 @@ name = "sheepd" version = "0.1.0" edition = "2024" +[lib] +name = "sheepd" +path = "src/lib.rs" + [[bin]] name = "sheepd" path = "src/sheepd.rs" +[[bin]] +name = "sheepctl" +path = "src/sheepctl.rs" + [[bin]] name = "herd" path = "src/herd.rs" @@ -38,3 +46,6 @@ sage = { git = "https://git.hydrar.de/jmarya/sage" } dashmap = "6.1.0" ulid = { version = "1.2.1", features = ["serde"] } chrono = "0.4.41" +directories = "6.0.0" +inquire = "0.7.5" +axum-extra = { version = "0.10.1", features = ["typed-header"] } diff --git a/src/api.rs b/src/api.rs index 773401f..63c61ba 100644 --- a/src/api.rs +++ b/src/api.rs @@ -3,6 +3,20 @@ use rumqttc::{AsyncClient, Event, EventLoop, MqttOptions, Packet, Transport}; use std::time::Duration; use tokio::time::sleep; +pub fn domain(host: &str) -> String { + if host.starts_with("http") { + return host.to_string(); + } else { + format!("https://{host}") + } +} + +#[derive(Deserialize, Serialize)] +pub struct LoginParam { + pub username: String, + pub password: String, +} + #[derive(Deserialize, Serialize)] /// Join Request pub struct JoinParams { @@ -89,20 +103,57 @@ where } #[derive(Deserialize, Serialize)] -/// Generic JSON API result -pub struct Result { - pub ok: u32, +pub struct DeviceList { + pub devices: Vec, } -impl Result { +#[derive(Deserialize, Serialize)] +pub struct DeviceEntry { + pub id: String, + pub hostname: String, + pub online: bool, +} + +#[derive(Deserialize, Serialize)] +/// Generic JSON API result +pub struct Result { + pub ok: Option, + pub err: Option, +} + +impl Result { + #[allow(non_snake_case)] + pub fn OkVal(val: T) -> Self { + Self { + ok: Some(val), + err: None, + } + } + + pub fn as_result(self) -> std::result::Result { + if let Some(ok) = self.ok { + Ok(ok) + } else { + Err(self.err.unwrap()) + } + } +} + +impl Result { #[allow(non_snake_case)] pub fn Ok() -> Self { - Self { ok: 1 } + Self { + ok: Some(1), + err: None, + } } #[allow(non_snake_case)] - pub fn Err() -> Self { - Self { ok: 0 } + pub fn Err(msg: &str) -> Self { + Self { + ok: None, + err: Some(msg.to_string()), + } } } diff --git a/src/herd.rs b/src/herd.rs index e2891cb..ab14824 100644 --- a/src/herd.rs +++ b/src/herd.rs @@ -11,11 +11,11 @@ use std::{net::SocketAddr, path::PathBuf}; mod api; mod herd_core; use crate::herd_core::mqtt::{handle_mqtt, listen_to_devices}; -use herd_core::model::Machine; use herd_core::{ config::Config, route::{join_device, login_user}, }; +use herd_core::{model::Machine, route::devices_list}; use sage::Identity; use tokio::sync::OnceCell; @@ -59,7 +59,9 @@ async fn main() { .layer(ClientIpSource::ConnectInfo.into_extension()); // Direct IP // .layer(ClientIpSource::XRealIp.into_extension()) // Proxy - let user = Router::new().route("/login", post(login_user)); + let user = Router::new() + .route("/login", post(login_user)) + .route("/devices", get(devices_list)); let app = Router::new().merge(device).merge(user); diff --git a/src/herd_core/mqtt.rs b/src/herd_core/mqtt.rs index 2b6323e..b309b4e 100644 --- a/src/herd_core/mqtt.rs +++ b/src/herd_core/mqtt.rs @@ -7,7 +7,7 @@ use owl::{Serialize, get, query}; use rumqttc::AsyncClient; use sage::PersonaIdentity; -fn is_within_80_seconds(time: chrono::DateTime) -> bool { +pub fn is_within_80_seconds(time: chrono::DateTime) -> bool { let now = chrono::Utc::now(); now.signed_duration_since(time).num_seconds() <= 80 } diff --git a/src/herd_core/route.rs b/src/herd_core/route.rs index 60bd1c6..93ad334 100644 --- a/src/herd_core/route.rs +++ b/src/herd_core/route.rs @@ -1,19 +1,55 @@ +use std::ops::Deref; + use crate::api; use crate::api::JoinResponse; +use crate::api::LoginParam; use crate::herd_core::model::Machine; use axum::Json; +use axum::extract::FromRequestParts; use axum::http::StatusCode; use axum_client_ip::ClientIp; +use axum_extra::TypedHeader; +use axum_extra::headers::Authorization; +use axum_extra::headers::authorization::Bearer; use based::auth::Sessions; use based::auth::User; +use owl::prelude::Model; +use owl::query; use owl::save; use serde::Deserialize; use serde_json::json; +use sheepd::DeviceEntry; +use sheepd::DeviceList; -#[derive(Deserialize)] -pub struct LoginParam { - username: String, - password: String, +use super::mqtt::is_within_80_seconds; + +pub async fn devices_list( + session: TypedHeader>, +) -> (StatusCode, Json>) { + let machines: Vec> = query!(|_| true); + + let mut ret = vec![]; + + for mac in machines { + let id = mac.read().id.to_string().replace("-", ""); + let online_state = crate::ONLINE + .get() + .unwrap() + .get(&id) + .map(|x| is_within_80_seconds(*x.deref())) + .unwrap_or(false); + + ret.push(DeviceEntry { + id: id, + hostname: mac.read().hostname.clone(), + online: online_state, + }); + } + + ( + StatusCode::OK, + Json(api::Result::OkVal(DeviceList { devices: ret })), + ) } pub async fn login_user(Json(payload): Json) -> (StatusCode, Json) { @@ -21,9 +57,15 @@ pub async fn login_user(Json(payload): Json) -> (StatusCode, Json match device_command.command { + DeviceCommands::List(list_devices_command) => list_devices(list_devices_command), + }, + SheepctlCommand::Login(login_command) => login(login_command), + } +} diff --git a/src/sheepctl_core/args.rs b/src/sheepctl_core/args.rs new file mode 100644 index 0000000..f9affdf --- /dev/null +++ b/src/sheepctl_core/args.rs @@ -0,0 +1,51 @@ +use argh::FromArgs; + +#[derive(FromArgs)] +/// Control your herd +pub struct SheepctlArgs { + #[argh(subcommand)] + pub command: SheepctlCommand, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +pub enum SheepctlCommand { + Login(LoginCommand), + Device(DeviceCommand), +} + +#[derive(FromArgs, PartialEq, Debug)] +/// Login to a homeserver +#[argh(subcommand, name = "login")] +pub struct LoginCommand { + #[argh(positional)] + /// homeserver + pub home: String, + + #[argh(positional)] + /// username + pub username: String, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand, name = "device")] +/// Commands for devices +pub struct DeviceCommand { + #[argh(subcommand)] + pub command: DeviceCommands, +} + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand)] +pub enum DeviceCommands { + List(ListDevicesCommand), +} + +#[derive(FromArgs, PartialEq, Debug)] +/// List devices +#[argh(subcommand, name = "ls")] +pub struct ListDevicesCommand { + #[argh(switch)] + /// only show online devices + pub online: bool, +} diff --git a/src/sheepctl_core/cmd.rs b/src/sheepctl_core/cmd.rs new file mode 100644 index 0000000..fe4c447 --- /dev/null +++ b/src/sheepctl_core/cmd.rs @@ -0,0 +1,105 @@ +use std::path::PathBuf; + +use owl::{Deserialize, Serialize}; +use sheepd::{DeviceList, LoginParam}; + +use super::args::{ListDevicesCommand, LoginCommand}; +use crate::api::domain; + +pub fn api_call Deserialize<'a>, I: Serialize>( + server: &str, + path: &str, + data: I, +) -> crate::api::Result { + let url = format!("{}/{path}", domain(server)); + let mut res = ureq::post(url).send_json(data).unwrap(); + let res: crate::api::Result = res.body_mut().read_json().unwrap(); + res +} + +pub fn api_call_get Deserialize<'a>>( + server: &str, + path: &str, + token: &str, +) -> crate::api::Result { + let url = format!("{}/{path}", domain(server)); + let mut res = ureq::get(url) + .header("Authorization", format!("Bearer {token}")) + .force_send_body() + .send_empty() + .unwrap(); + let res: crate::api::Result = res.body_mut().read_json().unwrap(); + res +} + +fn get_config_path() -> Option { + directories::ProjectDirs::from("de", "Hydrar", "sheepd") + .map(|proj_dirs| proj_dirs.config_dir().join("config.toml")) +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct CtlConfig { + pub home: String, + pub token: String, +} + +impl CtlConfig { + pub fn load() -> Option { + let c = std::fs::read_to_string(get_config_path()?).ok()?; + toml::from_str(&c).ok() + } + + pub fn save(&self) { + let s = toml::to_string(self).unwrap(); + let config = get_config_path().unwrap(); + let _ = std::fs::create_dir_all(config.parent().unwrap()); + std::fs::write(get_config_path().unwrap(), s).unwrap(); + } +} + +pub fn list_devices(arg: ListDevicesCommand) { + let conf = CtlConfig::load().unwrap(); + + if let Ok(devices) = api_call_get::(&conf.home, "devices", &conf.token).as_result() + { + println!("Hosts:"); + for d in devices.devices { + println!( + "- {} [{}]{}", + d.hostname, + d.id, + if d.online { " [ONLINE]" } else { "" } + ); + } + } +} + +pub fn login(arg: LoginCommand) { + if let Some(conf) = CtlConfig::load() { + println!("You are already logged in to {}", conf.home); + std::process::exit(1); + } + + let password = inquire::prompt_secret("Password: ").unwrap(); + + // login request + if let Result::Ok(token) = api_call::( + &arg.home, + "login", + LoginParam { + username: arg.username, + password: password, + }, + ) + .as_result() + { + // save token to config + CtlConfig { + home: arg.home, + token, + } + .save(); + } else { + println!("Login failed"); + } +} diff --git a/src/sheepctl_core/mod.rs b/src/sheepctl_core/mod.rs new file mode 100644 index 0000000..a786728 --- /dev/null +++ b/src/sheepctl_core/mod.rs @@ -0,0 +1,2 @@ +pub mod args; +pub mod cmd; diff --git a/src/sheepd_core/cmd.rs b/src/sheepd_core/cmd.rs index 8b1c7ec..02f0a0f 100644 --- a/src/sheepd_core/cmd.rs +++ b/src/sheepd_core/cmd.rs @@ -8,14 +8,7 @@ use crate::{ }; use super::args::JoinCommand; - -fn domain(host: &str) -> String { - if host.starts_with("http") { - return host.to_string(); - } else { - format!("https://{host}") - } -} +use crate::api::domain; /// Join a herd as client pub fn join(conf: JoinCommand) { From d61c836ada9ff68a887a5656ac545b37d4dc8a0e Mon Sep 17 00:00:00 2001 From: JMARyA Date: Mon, 5 May 2025 04:57:53 +0200 Subject: [PATCH 04/13] ci --- .woodpecker/build.yml | 15 +++++++++++++++ PKGBUILD | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 .woodpecker/build.yml create mode 100644 PKGBUILD diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml new file mode 100644 index 0000000..9d4bcb5 --- /dev/null +++ b/.woodpecker/build.yml @@ -0,0 +1,15 @@ +when: + - event: push + branch: main + +steps: + - name: "PKGBUILD" + image: git.hydrar.de/jmarya/pacco:latest + commands: + - pacco build --ci --push navos + environment: + PACCO_HOST: "https://pac.hydrar.de" + PACCO_TOKEN: + from_secret: pacco_token + SIGN_KEY: + from_secret: navos_key diff --git a/PKGBUILD b/PKGBUILD new file mode 100644 index 0000000..5f44e20 --- /dev/null +++ b/PKGBUILD @@ -0,0 +1,39 @@ +pkgbase=sheep +pkgname=('sheepd' 'sheepctl') +pkgver=2025.05.05_b010027 +pkgrel=1 +arch=('x86_64' 'aarch64') +url="https://git.hydrar.de/navos/sheepd" +license=('MIT') +makedepends=('rustup') +source=("repo::git+https://git.hydrar.de/navos/sheepd.git") +sha256sums=("SKIP") + +pkgver() { + cd "$srcdir/repo" + echo "$(date +%Y.%m.%d)_$(git rev-parse --short HEAD)" +} + +prepare() { + cd "$srcdir/repo" + rustup default nightly + cargo fetch +} + +build() { + cd "$srcdir/repo" + cargo build --release --bin sheepd + cargo build --release --bin sheepctl +} + +package_sheepd() { + pkgdesc="sheep daemon" + cd "$srcdir/repo" + install -Dm755 "target/release/sheepd" "$pkgdir/usr/bin/sheepd" +} + +package_sheepctl() { + pkgdesc="CLI for controling your herd" + cd "$srcdir/repo" + install -Dm755 "target/release/sheepctl" "$pkgdir/usr/bin/sheepctl" +} From 3af7b892b2189945282b73f5c1122e724fe14b1c Mon Sep 17 00:00:00 2001 From: JMARyA Date: Mon, 5 May 2025 05:17:11 +0200 Subject: [PATCH 05/13] docker --- Dockerfile | 18 ++++++++++++++++++ docker-compose.yml | 8 ++++++++ 2 files changed, 26 insertions(+) create mode 100644 Dockerfile diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..3635a90 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM rust:buster AS builder + +RUN rustup default nightly + +COPY . /app +WORKDIR /app + +RUN cargo build --release --bin herd --features herd + +FROM git.hydrar.de/navos/navos:latest + +RUN pacman-key --init && pacman-key --populate archlinux && pacman-key --populate navos && pacman -Syu --noconfirm && pacman -Syu --noconfirm openssl-1.1 + +COPY --from=builder /app/target/release/herd /herd + +WORKDIR / + +CMD ["/herd"] diff --git a/docker-compose.yml b/docker-compose.yml index 06250dd..caf61d4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,3 +10,11 @@ services: - ./mosquitto/data:/mosquitto/data - ./mosquitto/log:/mosquitto/log restart: unless-stopped + + herd: + build: . + ports: + - 8080:8000 + volumes: + - ./herd:/herd + restart: unless-stopped From f10c7df26236b7e6d7391019acf58f5f2b8a27b7 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Mon, 5 May 2025 14:56:02 +0200 Subject: [PATCH 06/13] =?UTF-8?q?=E2=9C=A8=20shell?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 362 ++++++-------------------------------- Cargo.toml | 3 +- src/api.rs | 15 ++ src/herd.rs | 10 +- src/herd_core/mqtt.rs | 30 +++- src/herd_core/route.rs | 81 ++++++++- src/sheepctl.rs | 3 +- src/sheepctl_core/args.rs | 10 ++ src/sheepctl_core/cmd.rs | 62 ++++++- src/sheepd_core/mqtt.rs | 26 ++- 10 files changed, 276 insertions(+), 326 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6fa03a4..1803d96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -333,9 +333,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288" +checksum = "021e862c184ae977658b36c4500f7feac3221ca5da43e3f25bd04ab6c79a29b5" dependencies = [ "axum-core", "axum-macros", @@ -358,7 +358,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower", "tower-layer", @@ -391,7 +391,7 @@ dependencies = [ "mime", "pin-project-lite", "rustversion", - "sync_wrapper 1.0.2", + "sync_wrapper", "tower-layer", "tower-service", "tracing", @@ -465,34 +465,21 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] -name = "based" +name = "based_auth" version = "0.1.0" -source = "git+https://git.hydrar.de/jmarya/based?branch=owl#2f2d77ec4897bfe0b5aeaf908c8ec2231fbd5c15" +source = "git+https://git.hydrar.de/jmarya/based_auth#3e3fd0fd83a2e75514c76fd8fe509470e9a752c7" dependencies = [ - "async-stream", "bcrypt", "chrono", - "dashmap", "data-encoding", "env_logger 0.10.2", - "futures", "hex", "log", - "maud", "owl", "rand 0.8.5", - "rayon", - "regex", - "reqwest", - "ring 0.16.20", "rocket", - "rocket_cors", "serde", - "serde_json", - "sqlx", - "tokio", "uuid", - "walkdir", ] [[package]] @@ -618,9 +605,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.20" +version = "1.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04da6a0d40b948dfc4fa8f5bbf402b0fc1a64a28dbf7d12ffd683550f2c1b63a" +checksum = "8691782945451c1c383942c4874dbe63814f61cb57ef773cda2972682b7bb3c0" dependencies = [ "shlex", ] @@ -892,9 +879,9 @@ dependencies = [ [[package]] name = "ct-codecs" -version = "1.1.3" +version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b916ba8ce9e4182696896f015e8a5ae6081b305f74690baa8465e35f5a142ea4" +checksum = "dd0d274c65cbc1c34703d2fc2ce0fb892ff68f4516b677671a2f238a30b9b2b2" [[package]] name = "ctr" @@ -1270,7 +1257,7 @@ checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095" dependencies = [ "futures-core", "futures-sink", - "spin 0.9.8", + "spin", ] [[package]] @@ -1571,9 +1558,9 @@ checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3" dependencies = [ "allocator-api2", "equivalent", @@ -1586,7 +1573,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.3", ] [[package]] @@ -1799,19 +1786,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "hyper-tls" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" -dependencies = [ - "bytes", - "hyper 0.14.32", - "native-tls", - "tokio", - "tokio-native-tls", -] - [[package]] name = "hyper-util" version = "0.1.11" @@ -2064,7 +2038,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "serde", ] @@ -2126,12 +2100,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b3f7cef34251886990511df1c61443aa928499d598a9473929ab5a90a527304" -[[package]] -name = "ipnet" -version = "2.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" - [[package]] name = "is-terminal" version = "0.4.16" @@ -2157,9 +2125,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a064218214dc6a10fbae5ec5fa888d80c45d611aba169222fc272072bf7aef6" +checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd" dependencies = [ "jiff-static", "log", @@ -2170,9 +2138,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.10" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "199b7932d97e325aff3a7030e141eafe7f2c6268e1d1b24859b753a627f45254" +checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300" dependencies = [ "proc-macro2", "quote", @@ -2195,7 +2163,7 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" dependencies = [ - "spin 0.9.8", + "spin", ] [[package]] @@ -2206,9 +2174,9 @@ checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "libm" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9627da5196e5d8ed0b0495e61e518847578da83483c37288316d9b2e03a7f72" +checksum = "a25169bd5913a4b437588a7e3d127cd6e90127b60e0ffbd834a38f1599e016b8" [[package]] name = "libredox" @@ -2295,28 +2263,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" -[[package]] -name = "maud" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df518b75016b4289cdddffa1b01f2122f4a49802c93191f3133f6dc2472ebcaa" -dependencies = [ - "itoa", - "maud_macros", -] - -[[package]] -name = "maud_macros" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa453238ec218da0af6b11fc5978d3b5c3a45ed97b722391a2a11f3306274e18" -dependencies = [ - "proc-macro-error", - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "md-5" version = "0.10.6" @@ -2402,7 +2348,7 @@ dependencies = [ "httparse", "memchr", "mime", - "spin 0.9.8", + "spin", "tokio", "tokio-util", "version_check", @@ -2578,9 +2524,9 @@ checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" [[package]] name = "openssl-sys" -version = "0.9.107" +version = "0.9.108" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8288979acd84749c744a9014b4382d42b8f7b2592847b5afb2ed29e5d16ede07" +checksum = "e145e1651e858e820e4860f7b9c5e169bc1d8ce1c86043be79fa7b7634821847" dependencies = [ "cc", "libc", @@ -2603,8 +2549,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "owl" version = "0.1.0" -source = "git+https://git.hydrar.de/red/owl#6c54873ca2e01035137cbce425fb346f785f045a" +source = "git+https://git.hydrar.de/red/owl#a858f0838a91ba42f69dd00e5624aac353fb7b69" dependencies = [ + "arc-swap", "argh", "chrono", "crossbeam", @@ -2631,7 +2578,7 @@ dependencies = [ [[package]] name = "owl_macro" version = "0.1.0" -source = "git+https://git.hydrar.de/red/owl#6c54873ca2e01035137cbce425fb346f785f045a" +source = "git+https://git.hydrar.de/red/owl#a858f0838a91ba42f69dd00e5624aac353fb7b69" dependencies = [ "convert_case", "heck", @@ -2845,29 +2792,6 @@ dependencies = [ "zerocopy", ] -[[package]] -name = "proc-macro-error" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" -dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "version_check", -] - -[[package]] -name = "proc-macro-error-attr" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" -dependencies = [ - "proc-macro2", - "quote", - "version_check", -] - [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -3008,9 +2932,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.11" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ "bitflags 2.9.0", ] @@ -3090,61 +3014,6 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" -[[package]] -name = "reqwest" -version = "0.11.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" -dependencies = [ - "base64 0.21.7", - "bytes", - "encoding_rs", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "hyper 0.14.32", - "hyper-tls", - "ipnet", - "js-sys", - "log", - "mime", - "native-tls", - "once_cell", - "percent-encoding", - "pin-project-lite", - "rustls-pemfile 1.0.4", - "serde", - "serde_json", - "serde_urlencoded", - "sync_wrapper 0.1.2", - "system-configuration", - "tokio", - "tokio-native-tls", - "tower-service", - "url", - "wasm-bindgen", - "wasm-bindgen-futures", - "web-sys", - "winreg", -] - -[[package]] -name = "ring" -version = "0.16.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" -dependencies = [ - "cc", - "libc", - "once_cell", - "spin 0.5.2", - "untrusted 0.7.1", - "web-sys", - "winapi", -] - [[package]] name = "ring" version = "0.17.14" @@ -3155,7 +3024,7 @@ dependencies = [ "cfg-if", "getrandom 0.2.16", "libc", - "untrusted 0.9.0", + "untrusted", "windows-sys 0.52.0", ] @@ -3248,23 +3117,6 @@ dependencies = [ "version_check", ] -[[package]] -name = "rocket_cors" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfac3a1df83f8d4fc96aa41dba3b86c786417b7fc0f52ec76295df2ba781aa69" -dependencies = [ - "http 0.2.12", - "log", - "regex", - "rocket", - "serde", - "serde_derive", - "unicase", - "unicase_serde", - "url", -] - [[package]] name = "rocket_http" version = "0.5.1" @@ -3346,7 +3198,7 @@ dependencies = [ "http 1.3.1", "log", "rustls-native-certs", - "rustls-pemfile 2.2.0", + "rustls-pemfile", "rustls-webpki 0.102.8", "thiserror 1.0.69", "tokio", @@ -3418,9 +3270,9 @@ dependencies = [ [[package]] name = "rustix" -version = "1.0.5" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ "bitflags 2.9.0", "errno", @@ -3436,7 +3288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf4ef73721ac7bcd79b2b315da7779d8fc09718c6b3d2d1b2d94850eb8c18432" dependencies = [ "log", - "ring 0.17.14", + "ring", "rustls-pki-types", "rustls-webpki 0.102.8", "subtle", @@ -3451,7 +3303,7 @@ checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" dependencies = [ "log", "once_cell", - "ring 0.17.14", + "ring", "rustls-pki-types", "rustls-webpki 0.103.1", "subtle", @@ -3465,21 +3317,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" dependencies = [ "openssl-probe", - "rustls-pemfile 2.2.0", + "rustls-pemfile", "rustls-pki-types", "schannel", "security-framework", ] -[[package]] -name = "rustls-pemfile" -version = "1.0.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c" -dependencies = [ - "base64 0.21.7", -] - [[package]] name = "rustls-pemfile" version = "2.2.0" @@ -3501,9 +3344,9 @@ version = "0.102.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" dependencies = [ - "ring 0.17.14", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -3512,9 +3355,9 @@ version = "0.103.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" dependencies = [ - "ring 0.17.14", + "ring", "rustls-pki-types", - "untrusted 0.9.0", + "untrusted", ] [[package]] @@ -3728,9 +3571,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -3754,8 +3597,9 @@ dependencies = [ "axum", "axum-client-ip", "axum-extra", - "based", + "based_auth", "chrono", + "crossbeam", "dashmap", "directories", "hex", @@ -3850,12 +3694,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "spin" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" - [[package]] name = "spin" version = "0.9.8" @@ -3905,7 +3743,7 @@ dependencies = [ "futures-intrusive", "futures-io", "futures-util", - "hashbrown 0.15.2", + "hashbrown 0.15.3", "hashlink", "indexmap", "log", @@ -4131,12 +3969,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "sync_wrapper" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" - [[package]] name = "sync_wrapper" version = "1.0.2" @@ -4145,36 +3977,15 @@ checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", "syn", ] -[[package]] -name = "system-configuration" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "system-configuration-sys", -] - -[[package]] -name = "system-configuration-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" -dependencies = [ - "core-foundation-sys", - "libc", -] - [[package]] name = "tempfile" version = "3.19.1" @@ -4332,16 +4143,6 @@ dependencies = [ "syn", ] -[[package]] -name = "tokio-native-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" -dependencies = [ - "native-tls", - "tokio", -] - [[package]] name = "tokio-rustls" version = "0.25.0" @@ -4436,7 +4237,7 @@ dependencies = [ "futures-core", "futures-util", "pin-project-lite", - "sync_wrapper 1.0.2", + "sync_wrapper", "tokio", "tower-layer", "tower-service", @@ -4608,22 +4409,6 @@ dependencies = [ "tinystr", ] -[[package]] -name = "unicase" -version = "2.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b844d17643ee918803943289730bec8aac480150456169e647ed0b576ba539" - -[[package]] -name = "unicase_serde" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef53697679d874d69f3160af80bc28de12730a985d57bdf2b47456ccb8b11f1" -dependencies = [ - "serde", - "unicase", -] - [[package]] name = "unicode-bidi" version = "0.3.18" @@ -4679,12 +4464,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "untrusted" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" - [[package]] name = "untrusted" version = "0.9.0" @@ -4703,7 +4482,7 @@ dependencies = [ "log", "percent-encoding", "rustls 0.23.26", - "rustls-pemfile 2.2.0", + "rustls-pemfile", "rustls-pki-types", "serde", "serde_json", @@ -4862,19 +4641,6 @@ dependencies = [ "wasm-bindgen-shared", ] -[[package]] -name = "wasm-bindgen-futures" -version = "0.4.50" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" -dependencies = [ - "cfg-if", - "js-sys", - "once_cell", - "wasm-bindgen", - "web-sys", -] - [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -4907,16 +4673,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "web-sys" -version = "0.3.77" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - [[package]] name = "web-time" version = "1.1.0" @@ -4929,9 +4685,9 @@ dependencies = [ [[package]] name = "webpki-roots" -version = "0.26.9" +version = "0.26.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29aad86cec885cafd03e8305fd727c418e970a521322c91688414d5b8efba16b" +checksum = "37493cadf42a2a939ed404698ded7fb378bf301b5011f973361779a3a74f8c93" dependencies = [ "rustls-pki-types", ] @@ -5195,23 +4951,13 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" -version = "0.7.7" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6cb8234a863ea0e8cd7284fcdd4f145233eb00fee02bbdd9861aec44e6477bc5" +checksum = "d9fb597c990f03753e08d3c29efbfcf2019a003b4bf4ba19225c158e1549f0f3" dependencies = [ "memchr", ] -[[package]] -name = "winreg" -version = "0.50.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" -dependencies = [ - "cfg-if", - "windows-sys 0.48.0", -] - [[package]] name = "wit-bindgen-rt" version = "0.39.0" diff --git a/Cargo.toml b/Cargo.toml index 6be1032..bfa45c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ axum-client-ip = { version = "1.0.0", optional = true } toml = "0.8.21" hex = "0.4.3" rand = "0.9.1" -based = { git = "https://git.hydrar.de/jmarya/based", branch = "owl" } +based_auth = { git = "https://git.hydrar.de/jmarya/based_auth" } http2 = "0.4.21" ureq = { version = "3.0.11", features = ["json"] } rumqttc = { version = "0.24.0", features = ["url", "websocket"] } @@ -49,3 +49,4 @@ chrono = "0.4.41" directories = "6.0.0" inquire = "0.7.5" axum-extra = { version = "0.10.1", features = ["typed-header"] } +crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] } diff --git a/src/api.rs b/src/api.rs index 63c61ba..7956a32 100644 --- a/src/api.rs +++ b/src/api.rs @@ -40,6 +40,19 @@ pub struct JoinResponse { pub mqtt: String, } +#[derive(Deserialize, Serialize)] +pub struct ShellParam { + pub cmd: String, + pub cwd: String, +} + +#[derive(Deserialize, Serialize, Debug)] +pub struct ShellResponse { + pub stdout: String, + pub stderr: String, + pub status: i32, +} + /// Setup a MQTT connection for `machine_id` on `mqtt`. /// /// This will connect either over `ws://` or `wss://` depending on the scheme of `mqtt`. By default it will use `wss://`. @@ -175,6 +188,7 @@ impl ClientAction { #[derive(Debug, Serialize, Deserialize)] pub enum ClientActions { OSQuery(String), + Shell(String, String), } #[derive(Debug, Serialize, Deserialize)] @@ -195,4 +209,5 @@ impl ServerResponse { #[derive(Debug, Serialize, Deserialize)] pub enum ServerResponses { OSQuery(String), + Shell(ShellResponse), } diff --git a/src/herd.rs b/src/herd.rs index ab14824..39bb952 100644 --- a/src/herd.rs +++ b/src/herd.rs @@ -1,3 +1,4 @@ +use api::ServerResponse; use axum::{ Router, routing::{get, post}, @@ -13,7 +14,7 @@ mod herd_core; use crate::herd_core::mqtt::{handle_mqtt, listen_to_devices}; use herd_core::{ config::Config, - route::{join_device, login_user}, + route::{device_get_api, device_shell_cmd, join_device, login_user}, }; use herd_core::{model::Machine, route::devices_list}; use sage::Identity; @@ -21,8 +22,11 @@ use tokio::sync::OnceCell; pub static IDENTITY: OnceCell = OnceCell::const_new(); pub static CONFIG: OnceCell = OnceCell::const_new(); +pub static MQTT: OnceCell = OnceCell::const_new(); pub static ONLINE: OnceCell>> = OnceCell::const_new(); +pub static DISPATCH: OnceCell>> = + OnceCell::const_new(); fn generate_token() -> String { let mut rng = rand::rng(); @@ -48,6 +52,7 @@ async fn main() { let _ = crate::CONFIG.set(config); crate::ONLINE.set(DashMap::new()).unwrap(); + crate::DISPATCH.set(DashMap::new()).unwrap(); let db = Database::filesystem("./herd/db"); set_global_db!(db); @@ -61,6 +66,8 @@ async fn main() { let user = Router::new() .route("/login", post(login_user)) + .route("/device/{device_id}", get(device_get_api)) + .route("/device/{device_id}/shell", post(device_shell_cmd)) .route("/devices", get(devices_list)); let app = Router::new().merge(device).merge(user); @@ -70,6 +77,7 @@ async fn main() { let (client, eventloop) = api::mqtt_connect("server", &crate::CONFIG.get().unwrap().mqtt); listen_to_devices(&client).await; + crate::MQTT.set(client); tokio::spawn(async { let listener = tokio::net::TcpListener::bind("0.0.0.0:8000").await.unwrap(); diff --git a/src/herd_core/mqtt.rs b/src/herd_core/mqtt.rs index b309b4e..a13095b 100644 --- a/src/herd_core/mqtt.rs +++ b/src/herd_core/mqtt.rs @@ -42,15 +42,33 @@ pub async fn handle_mqtt(topic: String, data: Vec) { } "respond" => { let resp: ServerResponse = serde_json::from_slice(&dec.payload).unwrap(); - log::info!("Got response {:?}", resp); + + let entry = crate::DISPATCH.get().unwrap().get(&resp.id).unwrap(); + entry.send(resp); } _ => {} } } +pub struct TaskWaiter { + pub id: ulid::Ulid, + pub recv: crossbeam::channel::Receiver, +} + +impl TaskWaiter { + pub async fn wait_for(&self, timeout: std::time::Duration) -> Option { + // TODO tokio spawn blocking? + self.recv.recv_timeout(timeout).ok() + } +} + /// Send a message to a registered `machine` -pub async fn send_msg(client: &AsyncClient, machine: &Model, request: T) { +pub async fn send_msg( + client: &AsyncClient, + machine: &Model, + request: ClientAction, +) -> TaskWaiter { let data = serde_json::to_string(&request).unwrap(); let pk = &machine.read().identity; let rec = pk.enc_key().unwrap(); @@ -66,6 +84,14 @@ pub async fn send_msg(client: &AsyncClient, machine: &Modelserver` topics diff --git a/src/herd_core/route.rs b/src/herd_core/route.rs index 93ad334..01ba4fd 100644 --- a/src/herd_core/route.rs +++ b/src/herd_core/route.rs @@ -1,9 +1,13 @@ use std::ops::Deref; use crate::api; +use crate::api::ClientAction; use crate::api::JoinResponse; use crate::api::LoginParam; +use crate::api::MachineAPI; +use crate::api::ShellResponse; use crate::herd_core::model::Machine; +use crate::herd_core::mqtt::listen_to_device; use axum::Json; use axum::extract::FromRequestParts; use axum::http::StatusCode; @@ -13,6 +17,7 @@ use axum_extra::headers::Authorization; use axum_extra::headers::authorization::Bearer; use based::auth::Sessions; use based::auth::User; +use owl::get; use owl::prelude::Model; use owl::query; use owl::save; @@ -20,25 +25,85 @@ use serde::Deserialize; use serde_json::json; use sheepd::DeviceEntry; use sheepd::DeviceList; +use ureq::http::StatusCode; use super::mqtt::is_within_80_seconds; +use super::mqtt::send_msg; + +pub async fn device_shell_cmd( + Path((device_id)): Path<(String)>, + Json(payload): Json, + session: TypedHeader>, +) -> (StatusCode, Json>) { + // TODO : check auth + + let machine: Option> = get!(device_id); + + if let Some(machine) = machine { + let resp = send_msg( + crate::MQTT.get(), + &machine, + ClientAction::new(ClientActions::Shell(payload.cmd, payload.cwd)), + ) + .await; + if let Some(resp) = resp.wait_for(std::time::Duration::from_secs(60)).await { + let r = match resp.response { + api::ServerResponses::Shell(shell_response) => shell_response, + _ => unreachable!(), + }; + (StatusCode::OK, Json(api::Result::OkVal(r))) + } else { + ( + StatusCode::BAD_GATEWAY, + Json(api::Result::Err("Did not receive response from device")), + ) + } + } else { + (StatusCode::NOT_FOUND, Json(api::Result::Err("Not Found"))) + } +} + +pub async fn device_get_api( + Path((device_id)): Path<(String)>, + session: TypedHeader>, +) -> (StatusCode, Json>) { + // TODO : check auth + + let machine: Option> = get!(device_id); + + if let Some(machine) = machine { + let api = machine.read(); + let api = DeviceEntry { + id: device_id, + hostname: api.hostname, + online: device_online(&device_id), + }; + (StatusCode::OK, Json(api::Result::OkVal(api))) + } else { + (StatusCode::NOT_FOUND, Json(api::Result::Err("Not Found"))) + } +} + +pub fn device_online(id: &str) -> bool { + crate::ONLINE + .get() + .unwrap() + .get(&id) + .map(|x| is_within_80_seconds(*x.deref())) + .unwrap_or(false) +} pub async fn devices_list( session: TypedHeader>, ) -> (StatusCode, Json>) { + // TODO : auth? let machines: Vec> = query!(|_| true); let mut ret = vec![]; for mac in machines { let id = mac.read().id.to_string().replace("-", ""); - let online_state = crate::ONLINE - .get() - .unwrap() - .get(&id) - .map(|x| is_within_80_seconds(*x.deref())) - .unwrap_or(false); - + let online_state = device_online(&id); ret.push(DeviceEntry { id: id, hostname: mac.read().hostname.clone(), @@ -86,7 +151,7 @@ pub async fn join_device( let machine = Machine::from_join_param(payload); let new_token = machine.token.clone(); - // TODO : add device listener instantly + listen_to_device(crate::MQTT.get(), &payload.machine_id).await; save!(machine); diff --git a/src/sheepctl.rs b/src/sheepctl.rs index 80947f7..c5fc437 100644 --- a/src/sheepctl.rs +++ b/src/sheepctl.rs @@ -1,6 +1,6 @@ use sheepctl_core::{ args::{DeviceCommands, SheepctlArgs, SheepctlCommand}, - cmd::{list_devices, login}, + cmd::{interactive_shell, list_devices, login}, }; mod api; @@ -14,5 +14,6 @@ fn main() { DeviceCommands::List(list_devices_command) => list_devices(list_devices_command), }, SheepctlCommand::Login(login_command) => login(login_command), + SheepctlCommand::Shell(shell_command) => interactive_shell(shell_command), } } diff --git a/src/sheepctl_core/args.rs b/src/sheepctl_core/args.rs index f9affdf..7357db0 100644 --- a/src/sheepctl_core/args.rs +++ b/src/sheepctl_core/args.rs @@ -12,6 +12,7 @@ pub struct SheepctlArgs { pub enum SheepctlCommand { Login(LoginCommand), Device(DeviceCommand), + Shell(ShellCommand), } #[derive(FromArgs, PartialEq, Debug)] @@ -49,3 +50,12 @@ pub struct ListDevicesCommand { /// only show online devices pub online: bool, } + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand, name = "shell")] +/// Enter interactive shell +pub struct ShellCommand { + #[argh(positional)] + /// device ID + pub device: String, +} diff --git a/src/sheepctl_core/cmd.rs b/src/sheepctl_core/cmd.rs index fe4c447..02990bd 100644 --- a/src/sheepctl_core/cmd.rs +++ b/src/sheepctl_core/cmd.rs @@ -1,11 +1,12 @@ use std::path::PathBuf; use owl::{Deserialize, Serialize}; -use sheepd::{DeviceList, LoginParam}; +use sheepd::{DeviceList, LoginParam, ShellResponse}; -use super::args::{ListDevicesCommand, LoginCommand}; -use crate::api::domain; +use super::args::{ListDevicesCommand, LoginCommand, ShellCommand}; +use crate::api::{DeviceEntry, ShellParam, domain}; +/// Make an POST API call to `path` with `data` returning `Result` pub fn api_call Deserialize<'a>, I: Serialize>( server: &str, path: &str, @@ -57,6 +58,11 @@ impl CtlConfig { } } +pub fn get_machine_api(home: &str, token: &str, id: &str) -> Option { + let res = api_call_get::(home, &format!("device/{id}"), token); + res.as_result().ok() +} + pub fn list_devices(arg: ListDevicesCommand) { let conf = CtlConfig::load().unwrap(); @@ -74,6 +80,56 @@ pub fn list_devices(arg: ListDevicesCommand) { } } +pub fn interactive_shell(arg: ShellCommand) { + let conf = CtlConfig::load().unwrap(); + let machine = arg.device; + + if let Some(machine) = get_machine_api(&conf.home, &conf.token, &machine) { + if !machine.online { + println!("Device not online."); + std::process::exit(1); + } + + let mut cwd = "/".to_string(); + + loop { + print!("{} [{}]: {cwd} $ ", machine.hostname, machine.id); + let mut read = String::new(); + std::io::stdin().read_line(&mut read).unwrap(); + if read == "exit" { + break; + } + + if read.starts_with("cd") { + let dir = read.trim_start_matches("cd ").trim_end_matches(";"); + cwd = dir.to_string(); + continue; + } + + let res = api_call::( + &conf.home, + &format!("device/{}/shell", machine.id), + ShellParam { + cmd: read.clone(), + cwd: cwd.clone(), + }, + ); + + if let Ok(resp) = res.as_result() { + println!("{} #{}\n{}", read, resp.status, resp.stdout); + + if !resp.stderr.is_empty() { + println!("Stderr: {}", resp.stderr); + } + } else { + println!("Command execution failed"); + } + } + } else { + println!("No device with ID {machine}"); + } +} + pub fn login(arg: LoginCommand) { if let Some(conf) = CtlConfig::load() { println!("You are already logged in to {}", conf.home); diff --git a/src/sheepd_core/mqtt.rs b/src/sheepd_core/mqtt.rs index c9b5ce6..20029c8 100644 --- a/src/sheepd_core/mqtt.rs +++ b/src/sheepd_core/mqtt.rs @@ -1,10 +1,10 @@ -use std::{os, process::Stdio}; +use std::process::Stdio; use owl::Serialize; use rumqttc::AsyncClient; use sage::PersonaIdentity; -use crate::api::{ClientAction, ServerResponse, ServerResponses}; +use crate::api::{ClientAction, ServerResponse, ServerResponses, ShellResponse}; // Client MQTT pub async fn handle_mqtt(topic: String, data: Vec) { @@ -31,6 +31,28 @@ pub async fn handle_mqtt(topic: String, data: Vec) { ) .await; } + crate::api::ClientActions::Shell(cmd, cwd) => { + log::info!("Received shell command: {cmd} in {cwd}"); + let res = std::process::Command::new("sh") + .arg("-c") + .arg(cmd) + .current_dir(cwd) + .output() + .unwrap(); + send_back( + crate::MQTT.get().unwrap(), + "respond", + ServerResponse::of( + &action, + ServerResponses::Shell(ShellResponse { + stdout: String::from_utf8_lossy(&res.stdout).to_string(), + stderr: String::from_utf8_lossy(&res.stderr).to_string(), + status: res.status.code().unwrap(), + }), + ), + ) + .await; + } } } From fedb81c485c5a5d750f94d73bf929c4078279b5c Mon Sep 17 00:00:00 2001 From: JMARyA Date: Mon, 5 May 2025 14:58:39 +0200 Subject: [PATCH 07/13] =?UTF-8?q?=E2=9C=A8=20systemd=20service?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .woodpecker/build.yml | 10 +++++++++- PKGBUILD | 1 + sheepd.service | 11 +++++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 sheepd.service diff --git a/.woodpecker/build.yml b/.woodpecker/build.yml index 9d4bcb5..d4d7443 100644 --- a/.woodpecker/build.yml +++ b/.woodpecker/build.yml @@ -1,3 +1,11 @@ +matrix: + platform: + - linux/amd64 + - linux/arm64 + +labels: + platform: ${platform} + when: - event: push branch: main @@ -12,4 +20,4 @@ steps: PACCO_TOKEN: from_secret: pacco_token SIGN_KEY: - from_secret: navos_key + from_secret: navos_key \ No newline at end of file diff --git a/PKGBUILD b/PKGBUILD index 5f44e20..41be468 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -29,6 +29,7 @@ build() { package_sheepd() { pkgdesc="sheep daemon" cd "$srcdir/repo" + install -Dm755 "sheepd.service" "$pkgir/etc/systemd/systemd/sheepd.service" install -Dm755 "target/release/sheepd" "$pkgdir/usr/bin/sheepd" } diff --git a/sheepd.service b/sheepd.service new file mode 100644 index 0000000..48a5802 --- /dev/null +++ b/sheepd.service @@ -0,0 +1,11 @@ +[Unit] +Description=Sheep Daemon +After=network.target + +[Service] +Type=simple +ExecStart=/usr/bin/sheepd +Restart=on-failure + +[Install] +WantedBy=multi-user.target From b58ccf4af32bc6b6290f65b7b50395c5609e1733 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 6 May 2025 02:21:56 +0200 Subject: [PATCH 08/13] =?UTF-8?q?=F0=9F=9A=91=EF=B8=8F=20fix?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.rs | 18 +++++++++--------- src/herd.rs | 7 ++++--- src/herd_core/mqtt.rs | 13 ++++++++++--- src/herd_core/route.rs | 33 +++++++++++++++++---------------- 4 files changed, 40 insertions(+), 31 deletions(-) diff --git a/src/api.rs b/src/api.rs index 7956a32..ab26d36 100644 --- a/src/api.rs +++ b/src/api.rs @@ -17,7 +17,7 @@ pub struct LoginParam { pub password: String, } -#[derive(Deserialize, Serialize)] +#[derive(Deserialize, Serialize, Clone)] /// Join Request pub struct JoinParams { /// Optional join token @@ -150,6 +150,14 @@ impl Result { Err(self.err.unwrap()) } } + + #[allow(non_snake_case)] + pub fn Err(msg: &str) -> Self { + Self { + ok: None, + err: Some(msg.to_string()), + } + } } impl Result { @@ -160,14 +168,6 @@ impl Result { err: None, } } - - #[allow(non_snake_case)] - pub fn Err(msg: &str) -> Self { - Self { - ok: None, - err: Some(msg.to_string()), - } - } } #[derive(Debug, Serialize, Deserialize)] diff --git a/src/herd.rs b/src/herd.rs index 39bb952..f9767cc 100644 --- a/src/herd.rs +++ b/src/herd.rs @@ -4,12 +4,12 @@ use axum::{ routing::{get, post}, }; use axum_client_ip::ClientIpSource; -use based::auth::User; use dashmap::DashMap; use owl::{prelude::*, set_global_db}; use rand::RngCore; use std::{net::SocketAddr, path::PathBuf}; mod api; +use based_auth::User; mod herd_core; use crate::herd_core::mqtt::{handle_mqtt, listen_to_devices}; use herd_core::{ @@ -17,6 +17,7 @@ use herd_core::{ route::{device_get_api, device_shell_cmd, join_device, login_user}, }; use herd_core::{model::Machine, route::devices_list}; +use rumqttc::AsyncClient; use sage::Identity; use tokio::sync::OnceCell; @@ -57,7 +58,7 @@ async fn main() { let db = Database::filesystem("./herd/db"); set_global_db!(db); - let _ = User::create("admin".to_string(), "admin", based::auth::UserRole::Admin).await; + let _ = User::create("admin".to_string(), "admin", based_auth::UserRole::Admin).await; let device = Router::new() .route("/join", post(join_device)) @@ -67,7 +68,7 @@ async fn main() { let user = Router::new() .route("/login", post(login_user)) .route("/device/{device_id}", get(device_get_api)) - .route("/device/{device_id}/shell", post(device_shell_cmd)) + // .route("/device/{device_id}/shell", post(device_shell_cmd)) .route("/devices", get(devices_list)); let app = Router::new().merge(device).merge(user); diff --git a/src/herd_core/mqtt.rs b/src/herd_core/mqtt.rs index a13095b..b3ca2e2 100644 --- a/src/herd_core/mqtt.rs +++ b/src/herd_core/mqtt.rs @@ -44,7 +44,11 @@ pub async fn handle_mqtt(topic: String, data: Vec) { let resp: ServerResponse = serde_json::from_slice(&dec.payload).unwrap(); log::info!("Got response {:?}", resp); - let entry = crate::DISPATCH.get().unwrap().get(&resp.id).unwrap(); + let entry = crate::DISPATCH + .get() + .unwrap() + .get(&resp.id.to_string()) + .unwrap(); entry.send(resp); } _ => {} @@ -85,8 +89,11 @@ pub async fn send_msg( .await .unwrap(); - let (sender, recv) = tokio::sync::mpsc::channel(100); - crate::DISPATCH.get().unwrap().insert(request.id, sender); + let (sender, recv) = crossbeam::channel::bounded(100); + crate::DISPATCH + .get() + .unwrap() + .insert(request.id.to_string(), sender); TaskWaiter { id: request.id, diff --git a/src/herd_core/route.rs b/src/herd_core/route.rs index 01ba4fd..1171464 100644 --- a/src/herd_core/route.rs +++ b/src/herd_core/route.rs @@ -2,21 +2,22 @@ use std::ops::Deref; use crate::api; use crate::api::ClientAction; +use crate::api::ClientActions; use crate::api::JoinResponse; use crate::api::LoginParam; -use crate::api::MachineAPI; use crate::api::ShellResponse; use crate::herd_core::model::Machine; use crate::herd_core::mqtt::listen_to_device; use axum::Json; use axum::extract::FromRequestParts; +use axum::extract::Path; use axum::http::StatusCode; use axum_client_ip::ClientIp; use axum_extra::TypedHeader; use axum_extra::headers::Authorization; use axum_extra::headers::authorization::Bearer; -use based::auth::Sessions; -use based::auth::User; +use based_auth::Sessions; +use based_auth::User; use owl::get; use owl::prelude::Model; use owl::query; @@ -25,13 +26,12 @@ use serde::Deserialize; use serde_json::json; use sheepd::DeviceEntry; use sheepd::DeviceList; -use ureq::http::StatusCode; use super::mqtt::is_within_80_seconds; use super::mqtt::send_msg; pub async fn device_shell_cmd( - Path((device_id)): Path<(String)>, + Path(device_id): Path, Json(payload): Json, session: TypedHeader>, ) -> (StatusCode, Json>) { @@ -41,7 +41,7 @@ pub async fn device_shell_cmd( if let Some(machine) = machine { let resp = send_msg( - crate::MQTT.get(), + crate::MQTT.get().unwrap(), &machine, ClientAction::new(ClientActions::Shell(payload.cmd, payload.cwd)), ) @@ -64,31 +64,32 @@ pub async fn device_shell_cmd( } pub async fn device_get_api( - Path((device_id)): Path<(String)>, + Path(device_id): Path, session: TypedHeader>, ) -> (StatusCode, Json>) { // TODO : check auth - let machine: Option> = get!(device_id); + let machine: Option> = get!(device_id.clone()); if let Some(machine) = machine { let api = machine.read(); let api = DeviceEntry { - id: device_id, - hostname: api.hostname, + id: device_id.clone(), + hostname: api.hostname.clone(), online: device_online(&device_id), }; (StatusCode::OK, Json(api::Result::OkVal(api))) } else { - (StatusCode::NOT_FOUND, Json(api::Result::Err("Not Found"))) + let res = api::Result::::Err("Not Found"); + (StatusCode::NOT_FOUND, Json(res)) } } -pub fn device_online(id: &str) -> bool { +pub fn device_online(id: &String) -> bool { crate::ONLINE .get() .unwrap() - .get(&id) + .get(id) .map(|x| is_within_80_seconds(*x.deref())) .unwrap_or(false) } @@ -129,7 +130,7 @@ pub async fn login_user(Json(payload): Json) -> (StatusCode, Json::Err("invalid"))), ) } } @@ -148,10 +149,10 @@ pub async fn join_device( payload.machine_id ); - let machine = Machine::from_join_param(payload); + let machine = Machine::from_join_param(payload.clone()); let new_token = machine.token.clone(); - listen_to_device(crate::MQTT.get(), &payload.machine_id).await; + listen_to_device(crate::MQTT.get().unwrap(), &payload.machine_id).await; save!(machine); From f904603a8dc2b95f981736cd9b40a6b6c90ac6b3 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 6 May 2025 09:09:03 +0200 Subject: [PATCH 09/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/herd.rs | 2 +- src/herd_core/route.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/herd.rs b/src/herd.rs index f9767cc..06348de 100644 --- a/src/herd.rs +++ b/src/herd.rs @@ -68,7 +68,7 @@ async fn main() { let user = Router::new() .route("/login", post(login_user)) .route("/device/{device_id}", get(device_get_api)) - // .route("/device/{device_id}/shell", post(device_shell_cmd)) + .route("/device/{device_id}/shell", post(device_shell_cmd)) .route("/devices", get(devices_list)); let app = Router::new().merge(device).merge(user); diff --git a/src/herd_core/route.rs b/src/herd_core/route.rs index 1171464..9753f70 100644 --- a/src/herd_core/route.rs +++ b/src/herd_core/route.rs @@ -32,8 +32,8 @@ use super::mqtt::send_msg; pub async fn device_shell_cmd( Path(device_id): Path, + TypedHeader(session): TypedHeader>, Json(payload): Json, - session: TypedHeader>, ) -> (StatusCode, Json>) { // TODO : check auth From 060209827f1778504bc440e0122b0cac88f039c7 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 6 May 2025 15:59:54 +0200 Subject: [PATCH 10/13] =?UTF-8?q?=E2=9C=A8=20async=20channel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 591 +++------------------------------------ Cargo.toml | 1 - src/herd.rs | 2 +- src/herd_core/mqtt.rs | 24 +- src/sheepctl_core/cmd.rs | 21 +- 5 files changed, 78 insertions(+), 561 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1803d96..950df40 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -233,39 +233,6 @@ dependencies = [ "serde", ] -[[package]] -name = "async-stream" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" -dependencies = [ - "async-stream-impl", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-stream-impl" -version = "0.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "async-trait" -version = "0.1.88" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "async-tungstenite" version = "0.25.1" @@ -304,21 +271,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "atomic" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" - -[[package]] -name = "atomic" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" -dependencies = [ - "bytemuck", -] - [[package]] name = "atomic-waker" version = "1.1.2" @@ -342,10 +294,10 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", - "hyper 1.6.0", + "hyper", "hyper-util", "itoa", "matchit", @@ -385,8 +337,8 @@ checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", "futures-core", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -408,8 +360,8 @@ dependencies = [ "bytes", "futures-util", "headers", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "http-body-util", "mime", "pin-project-lite", @@ -467,7 +419,7 @@ checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" [[package]] name = "based_auth" version = "0.1.0" -source = "git+https://git.hydrar.de/jmarya/based_auth#3e3fd0fd83a2e75514c76fd8fe509470e9a752c7" +source = "git+https://git.hydrar.de/jmarya/based_auth#bab73914bdddc53cbec5c1d2fabdcfe857838aa8" dependencies = [ "bcrypt", "chrono", @@ -477,7 +429,6 @@ dependencies = [ "log", "owl", "rand 0.8.5", - "rocket", "serde", "uuid", ] @@ -521,12 +472,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" -[[package]] -name = "binascii" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "383d29d513d8764dcdc42ea295d979eb99c3c9f00607b3692cf68a431f7dca72" - [[package]] name = "bitflags" version = "1.3.2" @@ -576,12 +521,6 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" -[[package]] -name = "bytemuck" -version = "1.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9134a6ef01ce4b366b50689c94f82c14bc72bc5d0386829828a2e2752ef7958c" - [[package]] name = "byteorder" version = "1.5.0" @@ -958,39 +897,6 @@ dependencies = [ "powerfmt", ] -[[package]] -name = "devise" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1d90b0c4c777a2cad215e3c7be59ac7c15adf45cf76317009b7d096d46f651d" -dependencies = [ - "devise_codegen", - "devise_core", -] - -[[package]] -name = "devise_codegen" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71b28680d8be17a570a2334922518be6adc3f58ecc880cbb404eaeb8624fd867" -dependencies = [ - "devise_core", - "quote", -] - -[[package]] -name = "devise_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b035a542cf7abf01f2e3c4d5a7acbaebfefe120ae4efc7bde3df98186e4b8af7" -dependencies = [ - "bitflags 2.9.0", - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - [[package]] name = "digest" version = "0.10.7" @@ -1065,15 +971,6 @@ dependencies = [ "serde", ] -[[package]] -name = "encoding_rs" -version = "0.8.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" -dependencies = [ - "cfg-if", -] - [[package]] name = "env_filter" version = "0.1.3" @@ -1160,20 +1057,6 @@ version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" -[[package]] -name = "figment" -version = "0.10.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" -dependencies = [ - "atomic 0.6.0", - "pear", - "serde", - "toml 0.8.22", - "uncased", - "version_check", -] - [[package]] name = "filetime" version = "0.2.25" @@ -1424,19 +1307,6 @@ dependencies = [ "byteorder", ] -[[package]] -name = "generator" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc16584ff22b460a382b7feec54b23d2908d858152e5739a120b949293bd74e" -dependencies = [ - "cc", - "libc", - "log", - "rustversion", - "windows", -] - [[package]] name = "generic-array" version = "0.14.7" @@ -1525,31 +1395,6 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" -[[package]] -name = "glob" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8d1add55171497b4705a648c6b583acafb01d58050a51727785f0b2c8e0a2b2" - -[[package]] -name = "h2" -version = "0.3.26" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" -dependencies = [ - "bytes", - "fnv", - "futures-core", - "futures-sink", - "futures-util", - "http 0.2.12", - "indexmap", - "slab", - "tokio", - "tokio-util", - "tracing", -] - [[package]] name = "hashbrown" version = "0.14.5" @@ -1585,7 +1430,7 @@ dependencies = [ "base64 0.21.7", "bytes", "headers-core", - "http 1.3.1", + "http", "httpdate", "mime", "sha1", @@ -1597,7 +1442,7 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "54b4a22553d4242c49fddb9ba998a99962b5cc6f22cb5a3482bec22522403ce4" dependencies = [ - "http 1.3.1", + "http", ] [[package]] @@ -1608,15 +1453,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" +checksum = "f154ce46856750ed433c8649605bf7ed2de3bc35fd9d2a9f30cddd873c80cb08" [[package]] name = "hex" @@ -1651,17 +1490,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.3.1" @@ -1673,17 +1501,6 @@ dependencies = [ "itoa", ] -[[package]] -name = "http-body" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" -dependencies = [ - "bytes", - "http 0.2.12", - "pin-project-lite", -] - [[package]] name = "http-body" version = "1.0.1" @@ -1691,7 +1508,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.3.1", + "http", ] [[package]] @@ -1702,8 +1519,8 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "pin-project-lite", ] @@ -1718,7 +1535,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.3.1", + "http", "indexmap", "slab", "tokio", @@ -1743,30 +1560,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b112acc8b3adf4b107a8ec20977da0273a8c386765a3ec0229bd500a1443f9f" -[[package]] -name = "hyper" -version = "0.14.32" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41dfc780fdec9373c01bae43289ea34c972e40ee3c9f6b3c8801a35f35586ce7" -dependencies = [ - "bytes", - "futures-channel", - "futures-core", - "futures-util", - "h2", - "http 0.2.12", - "http-body 0.4.6", - "httparse", - "httpdate", - "itoa", - "pin-project-lite", - "socket2", - "tokio", - "tower-service", - "tracing", - "want", -] - [[package]] name = "hyper" version = "1.6.0" @@ -1776,8 +1569,8 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.3.1", - "http-body 1.0.1", + "http", + "http-body", "httparse", "httpdate", "itoa", @@ -1794,9 +1587,9 @@ checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-util", - "http 1.3.1", - "http-body 1.0.1", - "hyper 1.6.0", + "http", + "http-body", + "hyper", "pin-project-lite", "tokio", "tower-service", @@ -2039,15 +1832,8 @@ checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", "hashbrown 0.15.3", - "serde", ] -[[package]] -name = "inlinable_string" -version = "0.1.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb" - [[package]] name = "inout" version = "0.1.4" @@ -2106,7 +1892,7 @@ version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ - "hermit-abi 0.5.0", + "hermit-abi", "libc", "windows-sys 0.59.0", ] @@ -2125,9 +1911,9 @@ checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d07d8d955d798e7a4d6f9c58cd1f1916e790b42b092758a9ef6e16fef9f1b3fd" +checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806" dependencies = [ "jiff-static", "log", @@ -2138,9 +1924,9 @@ dependencies = [ [[package]] name = "jiff-static" -version = "0.2.12" +version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f244cfe006d98d26f859c7abd1318d85327e1882dc9cef80f62daeeb0adcf300" +checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48" dependencies = [ "proc-macro2", "quote", @@ -2233,30 +2019,6 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" -[[package]] -name = "loom" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff50ecb28bb86013e935fb6683ab1f6d3a20016f123c76fd4c27470076ac30f5" -dependencies = [ - "cfg-if", - "generator", - "scoped-tls", - "serde", - "serde_json", - "tracing", - "tracing-subscriber", -] - -[[package]] -name = "matchers" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] - [[package]] name = "matchit" version = "0.8.4" @@ -2335,25 +2097,6 @@ dependencies = [ "windows-sys 0.52.0", ] -[[package]] -name = "multer" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83e87776546dc87511aa5ee218730c92b666d7264ab6ed41f9d215af9cd5224b" -dependencies = [ - "bytes", - "encoding_rs", - "futures-util", - "http 1.3.1", - "httparse", - "memchr", - "mime", - "spin", - "tokio", - "tokio-util", - "version_check", -] - [[package]] name = "native-tls" version = "0.2.14" @@ -2459,16 +2202,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" -dependencies = [ - "hermit-abi 0.3.9", - "libc", -] - [[package]] name = "object" version = "0.36.7" @@ -2632,29 +2365,6 @@ dependencies = [ "hmac", ] -[[package]] -name = "pear" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdeeaa00ce488657faba8ebf44ab9361f9365a97bd39ffb8a60663f57ff4b467" -dependencies = [ - "inlinable_string", - "pear_codegen", - "yansi", -] - -[[package]] -name = "pear_codegen" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4bab5b985dc082b345f812b7df84e1bef27e7207b39e448439ba8bd69c93f147" -dependencies = [ - "proc-macro2", - "proc-macro2-diagnostics", - "quote", - "syn", -] - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2823,19 +2533,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "proc-macro2-diagnostics" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8" -dependencies = [ - "proc-macro2", - "quote", - "syn", - "version_check", - "yansi", -] - [[package]] name = "quote" version = "1.0.40" @@ -2950,26 +2647,6 @@ dependencies = [ "thiserror 2.0.12", ] -[[package]] -name = "ref-cast" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a0ae411dbe946a674d89546582cea4ba2bb8defac896622d6496f14c23ba5cf" -dependencies = [ - "ref-cast-impl", -] - -[[package]] -name = "ref-cast-impl" -version = "1.0.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1165225c21bff1f3bbce98f5a1f889949bc902d3575308cc7b0de30b4f6d27c7" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "regex" version = "1.11.1" @@ -2978,17 +2655,8 @@ checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", -] - -[[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" -dependencies = [ - "regex-syntax 0.6.29", + "regex-automata", + "regex-syntax", ] [[package]] @@ -2999,15 +2667,9 @@ checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-syntax", ] -[[package]] -name = "regex-syntax" -version = "0.6.29" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" - [[package]] name = "regex-syntax" version = "0.8.5" @@ -3062,88 +2724,6 @@ dependencies = [ "serde_bytes", ] -[[package]] -name = "rocket" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a516907296a31df7dc04310e7043b61d71954d703b603cc6867a026d7e72d73f" -dependencies = [ - "async-stream", - "async-trait", - "atomic 0.5.3", - "binascii", - "bytes", - "either", - "figment", - "futures", - "indexmap", - "log", - "memchr", - "multer", - "num_cpus", - "parking_lot", - "pin-project-lite", - "rand 0.8.5", - "ref-cast", - "rocket_codegen", - "rocket_http", - "serde", - "serde_json", - "state", - "tempfile", - "time", - "tokio", - "tokio-stream", - "tokio-util", - "ubyte", - "version_check", - "yansi", -] - -[[package]] -name = "rocket_codegen" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "575d32d7ec1a9770108c879fc7c47815a80073f96ca07ff9525a94fcede1dd46" -dependencies = [ - "devise", - "glob", - "indexmap", - "proc-macro2", - "quote", - "rocket_http", - "syn", - "unicode-xid", - "version_check", -] - -[[package]] -name = "rocket_http" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e274915a20ee3065f611c044bd63c40757396b6dbc057d6046aec27f14f882b9" -dependencies = [ - "cookie", - "either", - "futures", - "http 0.2.12", - "hyper 0.14.32", - "indexmap", - "log", - "memchr", - "pear", - "percent-encoding", - "pin-project-lite", - "ref-cast", - "serde", - "smallvec", - "stable-pattern", - "state", - "time", - "tokio", - "uncased", -] - [[package]] name = "rpassword" version = "7.4.0" @@ -3195,7 +2775,7 @@ dependencies = [ "bytes", "flume", "futures-util", - "http 1.3.1", + "http", "log", "rustls-native-certs", "rustls-pemfile", @@ -3209,9 +2789,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.7.0" +version = "8.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5fbc0ee50fcb99af7cebb442e5df7b5b45e9460ffa3f8f549cd26b862bec49d" +checksum = "60e425e204264b144d4c929d126d0de524b40a961686414bab5040f7465c71be" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -3297,15 +2877,15 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.26" +version = "0.23.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df51b5869f3a441595eac5e8ff14d486ff285f7b8c0df8770e49c3b56351f0f0" +checksum = "730944ca083c1c233a75c09f199e973ca499344a2b7ba9e755c457e86fb4a321" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.1", + "rustls-webpki 0.103.2", "subtle", "zeroize", ] @@ -3351,9 +2931,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "7149975849f1abb3832b246010ef62ccc80d3a76169517ada7188252b9cfb437" dependencies = [ "ring", "rustls-pki-types", @@ -3410,12 +2990,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - [[package]] name = "scopeguard" version = "1.2.0" @@ -3599,7 +3173,6 @@ dependencies = [ "axum-extra", "based_auth", "chrono", - "crossbeam", "dashmap", "directories", "hex", @@ -3911,30 +3484,12 @@ dependencies = [ "uuid", ] -[[package]] -name = "stable-pattern" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4564168c00635f88eaed410d5efa8131afa8d8699a612c80c455a0ba05c21045" -dependencies = [ - "memchr", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" -[[package]] -name = "state" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b8c4a4445d81357df8b1a650d0d0d6fbbbfe99d064aa5e02f3e4022061476d8" -dependencies = [ - "loom", -] - [[package]] name = "stringprep" version = "0.1.5" @@ -4116,9 +3671,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.44.2" +version = "1.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6b88822cbe49de4185e3a4cbf8321dd487cf5fe0c5c65695fef6346371e9c48" +checksum = "2513ca694ef9ede0fb23fe71a4ee4107cb102b9dc1930f6d0fd77aae068ae165" dependencies = [ "backtrace", "bytes", @@ -4306,24 +3861,14 @@ version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ - "matchers", "nu-ansi-term", - "once_cell", - "regex", "sharded-slab", "smallvec", "thread_local", - "tracing", "tracing-core", "tracing-log", ] -[[package]] -name = "try-lock" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" - [[package]] name = "tungstenite" version = "0.21.0" @@ -4333,7 +3878,7 @@ dependencies = [ "byteorder", "bytes", "data-encoding", - "http 1.3.1", + "http", "httparse", "log", "rand 0.8.5", @@ -4360,15 +3905,6 @@ version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" -[[package]] -name = "ubyte" -version = "0.10.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f720def6ce1ee2fc44d40ac9ed6d3a59c361c80a75a7aa8e75bb9baed31cf2ea" -dependencies = [ - "serde", -] - [[package]] name = "ulid" version = "1.2.1" @@ -4380,16 +3916,6 @@ dependencies = [ "web-time", ] -[[package]] -name = "uncased" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" -dependencies = [ - "serde", - "version_check", -] - [[package]] name = "unic-langid" version = "0.9.5" @@ -4448,12 +3974,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - [[package]] name = "universal-hash" version = "0.5.1" @@ -4481,7 +4001,7 @@ dependencies = [ "flate2", "log", "percent-encoding", - "rustls 0.23.26", + "rustls 0.23.27", "rustls-pemfile", "rustls-pki-types", "serde", @@ -4498,7 +4018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fadf18427d33828c311234884b7ba2afb57143e6e7e69fda7ee883b624661e36" dependencies = [ "base64 0.22.1", - "http 1.3.1", + "http", "httparse", "log", ] @@ -4585,15 +4105,6 @@ dependencies = [ "winapi-util", ] -[[package]] -name = "want" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" -dependencies = [ - "try-lock", -] - [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -4733,15 +4244,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e686886bc078bc1b0b600cac0147aadb815089b6e4da64016cbd754b6342700f" -dependencies = [ - "windows-targets 0.48.5", -] - [[package]] name = "windows-core" version = "0.61.0" @@ -5023,15 +4525,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "yansi" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -dependencies = [ - "is-terminal", -] - [[package]] name = "yoke" version = "0.7.5" diff --git a/Cargo.toml b/Cargo.toml index bfa45c1..5c14301 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,4 +49,3 @@ chrono = "0.4.41" directories = "6.0.0" inquire = "0.7.5" axum-extra = { version = "0.10.1", features = ["typed-header"] } -crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] } diff --git a/src/herd.rs b/src/herd.rs index 06348de..f1c9305 100644 --- a/src/herd.rs +++ b/src/herd.rs @@ -26,7 +26,7 @@ pub static CONFIG: OnceCell = OnceCell::const_new(); pub static MQTT: OnceCell = OnceCell::const_new(); pub static ONLINE: OnceCell>> = OnceCell::const_new(); -pub static DISPATCH: OnceCell>> = +pub static DISPATCH: OnceCell>> = OnceCell::const_new(); fn generate_token() -> String { diff --git a/src/herd_core/mqtt.rs b/src/herd_core/mqtt.rs index b3ca2e2..2c4b5be 100644 --- a/src/herd_core/mqtt.rs +++ b/src/herd_core/mqtt.rs @@ -44,12 +44,17 @@ pub async fn handle_mqtt(topic: String, data: Vec) { let resp: ServerResponse = serde_json::from_slice(&dec.payload).unwrap(); log::info!("Got response {:?}", resp); - let entry = crate::DISPATCH + let (id, entry) = crate::DISPATCH .get() .unwrap() - .get(&resp.id.to_string()) + .remove(&resp.id.to_string()) .unwrap(); - entry.send(resp); + + if entry.send(resp).is_err() { + log::error!( + "Could not send back response for action {id}. Probably due to timeout" + ); + } } _ => {} } @@ -57,13 +62,16 @@ pub async fn handle_mqtt(topic: String, data: Vec) { pub struct TaskWaiter { pub id: ulid::Ulid, - pub recv: crossbeam::channel::Receiver, + pub recv: tokio::sync::oneshot::Receiver, } impl TaskWaiter { - pub async fn wait_for(&self, timeout: std::time::Duration) -> Option { - // TODO tokio spawn blocking? - self.recv.recv_timeout(timeout).ok() + pub async fn wait_for(self, timeout: std::time::Duration) -> Option { + if let Ok(in_time) = tokio::time::timeout(timeout, self.recv).await { + return in_time.ok(); + } + + None } } @@ -89,7 +97,7 @@ pub async fn send_msg( .await .unwrap(); - let (sender, recv) = crossbeam::channel::bounded(100); + let (sender, recv) = tokio::sync::oneshot::channel(); crate::DISPATCH .get() .unwrap() diff --git a/src/sheepctl_core/cmd.rs b/src/sheepctl_core/cmd.rs index 02990bd..216a363 100644 --- a/src/sheepctl_core/cmd.rs +++ b/src/sheepctl_core/cmd.rs @@ -1,4 +1,4 @@ -use std::path::PathBuf; +use std::{io::Write, path::PathBuf}; use owl::{Deserialize, Serialize}; use sheepd::{DeviceList, LoginParam, ShellResponse}; @@ -18,6 +18,21 @@ pub fn api_call Deserialize<'a>, I: Serialize>( res } +pub fn api_call_post_auth Deserialize<'a>, I: Serialize>( + server: &str, + path: &str, + token: &str, + data: I, +) -> crate::api::Result { + let url = format!("{}/{path}", domain(server)); + let mut res = ureq::post(url) + .header("Authorization", format!("Bearer {token}")) + .send_json(data) + .unwrap(); + let res: crate::api::Result = res.body_mut().read_json().unwrap(); + res +} + pub fn api_call_get Deserialize<'a>>( server: &str, path: &str, @@ -94,6 +109,7 @@ pub fn interactive_shell(arg: ShellCommand) { loop { print!("{} [{}]: {cwd} $ ", machine.hostname, machine.id); + std::io::stdout().flush().unwrap(); let mut read = String::new(); std::io::stdin().read_line(&mut read).unwrap(); if read == "exit" { @@ -106,9 +122,10 @@ pub fn interactive_shell(arg: ShellCommand) { continue; } - let res = api_call::( + let res = api_call_post_auth::( &conf.home, &format!("device/{}/shell", machine.id), + &conf.token, ShellParam { cmd: read.clone(), cwd: cwd.clone(), From 1c3a136730b6c2fd2419a56d061e04c3193932de Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 6 May 2025 16:03:41 +0200 Subject: [PATCH 11/13] =?UTF-8?q?=E2=9C=A8=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 3 +++ Cargo.toml | 2 +- src/herd_core/route.rs | 27 +++++++++++++++++++-------- 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 950df40..8a0545a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -358,6 +358,7 @@ dependencies = [ "axum", "axum-core", "bytes", + "cookie", "futures-util", "headers", "http", @@ -421,6 +422,8 @@ name = "based_auth" version = "0.1.0" source = "git+https://git.hydrar.de/jmarya/based_auth#bab73914bdddc53cbec5c1d2fabdcfe857838aa8" dependencies = [ + "axum", + "axum-extra", "bcrypt", "chrono", "data-encoding", diff --git a/Cargo.toml b/Cargo.toml index 5c14301..9406126 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -38,7 +38,7 @@ axum-client-ip = { version = "1.0.0", optional = true } toml = "0.8.21" hex = "0.4.3" rand = "0.9.1" -based_auth = { git = "https://git.hydrar.de/jmarya/based_auth" } +based_auth = { git = "https://git.hydrar.de/jmarya/based_auth", features = ["axum"] } http2 = "0.4.21" ureq = { version = "3.0.11", features = ["json"] } rumqttc = { version = "0.24.0", features = ["url", "websocket"] } diff --git a/src/herd_core/route.rs b/src/herd_core/route.rs index 9753f70..605e2a1 100644 --- a/src/herd_core/route.rs +++ b/src/herd_core/route.rs @@ -16,6 +16,7 @@ use axum_client_ip::ClientIp; use axum_extra::TypedHeader; use axum_extra::headers::Authorization; use axum_extra::headers::authorization::Bearer; +use based_auth::APIUser; use based_auth::Sessions; use based_auth::User; use owl::get; @@ -30,12 +31,23 @@ use sheepd::DeviceList; use super::mqtt::is_within_80_seconds; use super::mqtt::send_msg; +macro_rules! check_admin { + ($user:ident) => { + if !$user.read().is_admin() { + return ( + StatusCode::UNAUTHORIZED, + Json(api::Result::Err("Invalid credentials")), + ); + } + }; +} + pub async fn device_shell_cmd( Path(device_id): Path, - TypedHeader(session): TypedHeader>, + APIUser(user): APIUser, Json(payload): Json, ) -> (StatusCode, Json>) { - // TODO : check auth + check_admin!(user); let machine: Option> = get!(device_id); @@ -65,9 +77,9 @@ pub async fn device_shell_cmd( pub async fn device_get_api( Path(device_id): Path, - session: TypedHeader>, + APIUser(user): APIUser, ) -> (StatusCode, Json>) { - // TODO : check auth + check_admin!(user); let machine: Option> = get!(device_id.clone()); @@ -94,10 +106,9 @@ pub fn device_online(id: &String) -> bool { .unwrap_or(false) } -pub async fn devices_list( - session: TypedHeader>, -) -> (StatusCode, Json>) { - // TODO : auth? +pub async fn devices_list(APIUser(user): APIUser) -> (StatusCode, Json>) { + check_admin!(user); + let machines: Vec> = query!(|_| true); let mut ret = vec![]; From 5c53c57d74c24bed1786ef804b59e7a04e51aaad Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 6 May 2025 22:48:24 +0200 Subject: [PATCH 12/13] =?UTF-8?q?=E2=9C=A8=20osquery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api.rs | 5 +++++ src/herd.rs | 3 ++- src/herd_core/route.rs | 33 +++++++++++++++++++++++++++++++++ src/sheepctl.rs | 3 ++- src/sheepctl_core/args.rs | 13 +++++++++++++ src/sheepctl_core/cmd.rs | 33 +++++++++++++++++++++++++++++++-- 6 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/api.rs b/src/api.rs index ab26d36..a285fac 100644 --- a/src/api.rs +++ b/src/api.rs @@ -46,6 +46,11 @@ pub struct ShellParam { pub cwd: String, } +#[derive(Deserialize, Serialize)] +pub struct QueryParam { + pub query: String, +} + #[derive(Deserialize, Serialize, Debug)] pub struct ShellResponse { pub stdout: String, diff --git a/src/herd.rs b/src/herd.rs index f1c9305..c45f866 100644 --- a/src/herd.rs +++ b/src/herd.rs @@ -14,7 +14,7 @@ mod herd_core; use crate::herd_core::mqtt::{handle_mqtt, listen_to_devices}; use herd_core::{ config::Config, - route::{device_get_api, device_shell_cmd, join_device, login_user}, + route::{device_get_api, device_osquery, device_shell_cmd, join_device, login_user}, }; use herd_core::{model::Machine, route::devices_list}; use rumqttc::AsyncClient; @@ -69,6 +69,7 @@ async fn main() { .route("/login", post(login_user)) .route("/device/{device_id}", get(device_get_api)) .route("/device/{device_id}/shell", post(device_shell_cmd)) + .route("/device/{device_id}/osquery", post(device_osquery)) .route("/devices", get(devices_list)); let app = Router::new().merge(device).merge(user); diff --git a/src/herd_core/route.rs b/src/herd_core/route.rs index 605e2a1..48e8b54 100644 --- a/src/herd_core/route.rs +++ b/src/herd_core/route.rs @@ -42,6 +42,39 @@ macro_rules! check_admin { }; } +pub async fn device_osquery( + Path(device_id): Path, + APIUser(user): APIUser, + Json(payload): Json, +) -> (StatusCode, Json>) { + check_admin!(user); + + let machine: Option> = get!(device_id); + + if let Some(machine) = machine { + let resp = send_msg( + crate::MQTT.get().unwrap(), + &machine, + ClientAction::new(ClientActions::OSQuery(payload.query)), + ) + .await; + if let Some(resp) = resp.wait_for(std::time::Duration::from_secs(60)).await { + let r = match resp.response { + api::ServerResponses::OSQuery(res) => res, + _ => unreachable!(), + }; + (StatusCode::OK, Json(api::Result::OkVal(r))) + } else { + ( + StatusCode::BAD_GATEWAY, + Json(api::Result::Err("Did not receive response from device")), + ) + } + } else { + (StatusCode::NOT_FOUND, Json(api::Result::Err("Not Found"))) + } +} + pub async fn device_shell_cmd( Path(device_id): Path, APIUser(user): APIUser, diff --git a/src/sheepctl.rs b/src/sheepctl.rs index c5fc437..2c9bf28 100644 --- a/src/sheepctl.rs +++ b/src/sheepctl.rs @@ -1,6 +1,6 @@ use sheepctl_core::{ args::{DeviceCommands, SheepctlArgs, SheepctlCommand}, - cmd::{interactive_shell, list_devices, login}, + cmd::{interactive_shell, list_devices, login, run_osquery}, }; mod api; @@ -15,5 +15,6 @@ fn main() { }, SheepctlCommand::Login(login_command) => login(login_command), SheepctlCommand::Shell(shell_command) => interactive_shell(shell_command), + SheepctlCommand::Query(query_command) => run_osquery(query_command), } } diff --git a/src/sheepctl_core/args.rs b/src/sheepctl_core/args.rs index 7357db0..09ea27f 100644 --- a/src/sheepctl_core/args.rs +++ b/src/sheepctl_core/args.rs @@ -13,6 +13,7 @@ pub enum SheepctlCommand { Login(LoginCommand), Device(DeviceCommand), Shell(ShellCommand), + Query(QueryCommand), } #[derive(FromArgs, PartialEq, Debug)] @@ -59,3 +60,15 @@ pub struct ShellCommand { /// device ID pub device: String, } + +#[derive(FromArgs, PartialEq, Debug)] +#[argh(subcommand, name = "query")] +/// Run an osquery +pub struct QueryCommand { + #[argh(positional)] + /// device ID + pub device: String, + #[argh(positional)] + /// query + pub query: String, +} diff --git a/src/sheepctl_core/cmd.rs b/src/sheepctl_core/cmd.rs index 216a363..5812607 100644 --- a/src/sheepctl_core/cmd.rs +++ b/src/sheepctl_core/cmd.rs @@ -3,8 +3,8 @@ use std::{io::Write, path::PathBuf}; use owl::{Deserialize, Serialize}; use sheepd::{DeviceList, LoginParam, ShellResponse}; -use super::args::{ListDevicesCommand, LoginCommand, ShellCommand}; -use crate::api::{DeviceEntry, ShellParam, domain}; +use super::args::{ListDevicesCommand, LoginCommand, QueryCommand, ShellCommand}; +use crate::api::{DeviceEntry, QueryParam, ShellParam, domain}; /// Make an POST API call to `path` with `data` returning `Result` pub fn api_call Deserialize<'a>, I: Serialize>( @@ -147,6 +147,35 @@ pub fn interactive_shell(arg: ShellCommand) { } } +pub fn run_osquery(args: QueryCommand) { + // TODO : sanity checks + + let conf = CtlConfig::load().unwrap(); + let machine = args.device; + + if let Some(machine) = get_machine_api(&conf.home, &conf.token, &machine) { + if !machine.online { + println!("Device not online."); + std::process::exit(1); + } + + let res = api_call_post_auth::( + &conf.home, + &format!("device/{}/osquery", machine.id), + &conf.token, + QueryParam { query: args.query }, + ); + + if let Ok(res) = res.as_result() { + println!("{res}"); + } else { + println!("Error doing query"); + } + } else { + println!("No device with ID {machine}"); + } +} + pub fn login(arg: LoginCommand) { if let Some(conf) = CtlConfig::load() { println!("You are already logged in to {}", conf.home); From 62924c5edcd9f6fd8b69d52ac5ee8fb7b47f0758 Mon Sep 17 00:00:00 2001 From: JMARyA Date: Tue, 6 May 2025 23:14:29 +0200 Subject: [PATCH 13/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20refactor=20osquery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PKGBUILD | 1 + src/sheepd_core/mqtt.rs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/PKGBUILD b/PKGBUILD index 41be468..1af5275 100644 --- a/PKGBUILD +++ b/PKGBUILD @@ -28,6 +28,7 @@ build() { package_sheepd() { pkgdesc="sheep daemon" + depends=('osquery') cd "$srcdir/repo" install -Dm755 "sheepd.service" "$pkgir/etc/systemd/systemd/sheepd.service" install -Dm755 "target/release/sheepd" "$pkgdir/usr/bin/sheepd" diff --git a/src/sheepd_core/mqtt.rs b/src/sheepd_core/mqtt.rs index 20029c8..59300d0 100644 --- a/src/sheepd_core/mqtt.rs +++ b/src/sheepd_core/mqtt.rs @@ -21,7 +21,6 @@ pub async fn handle_mqtt(topic: String, data: Vec) { match &action.action { crate::api::ClientActions::OSQuery(query) => { - // TODO : run osquery log::info!("Doing osquery with {query}"); let res = osquery(&query); send_back(