small but useful fixes

* optimized error handling
* more usage of tracing-subscriber
* fixed some integration test bugs
* fixed db constraints
pull/1/head
plazmoid 2 years ago
parent c70cdbd262
commit a50e6d242f
  1. 39
      bin/u_agent/src/lib.rs
  2. 1
      bin/u_panel/Cargo.toml
  3. 5
      bin/u_panel/src/argparse.rs
  4. 5
      bin/u_panel/src/main.rs
  5. 2
      bin/u_panel/src/server/mod.rs
  6. 2
      bin/u_server/Cargo.toml
  7. 2
      bin/u_server/src/handlers.rs
  8. 9
      bin/u_server/src/main.rs
  9. 6
      bin/u_server/src/u_server.rs
  10. 3
      integration/Cargo.toml
  11. 15
      integration/docker-compose.yml
  12. 2
      integration/tests/fixtures/agent.rs
  13. 6
      integration/tests/helpers/panel.rs
  14. 20
      integration/tests/integration/behaviour.rs
  15. 3
      lib/u_lib/Cargo.toml
  16. 59
      lib/u_lib/src/api.rs
  17. 2
      lib/u_lib/src/builder.rs
  18. 34
      lib/u_lib/src/errors/chan.rs
  19. 23
      lib/u_lib/src/logging.rs
  20. 2
      lib/u_lib/src/messaging/mod.rs
  21. 7
      lib/u_lib/src/models/jobs/meta.rs
  22. 13
      lib/u_lib/src/utils/combined_result.rs
  23. 2
      lib/u_lib/src/utils/fmt/stripped.rs
  24. 2
      lib/u_lib/src/utils/proc_output.rs
  25. 8
      migrations/2020-10-24-111622_create_all/up.sql

@ -7,7 +7,6 @@
extern crate log; extern crate log;
use daemonize::Daemonize; use daemonize::Daemonize;
use std::panic;
use std::sync::Arc; use std::sync::Arc;
use tokio::time::{sleep, Duration}; use tokio::time::{sleep, Duration};
use u_lib::{ use u_lib::{
@ -22,7 +21,7 @@ pub async fn process_request(job_requests: Vec<AssignedJob>, client: &ClientHand
if !job_requests.is_empty() { if !job_requests.is_empty() {
for jr in &job_requests { for jr in &job_requests {
if !JobCache::contains(jr.job_id) { if !JobCache::contains(jr.job_id) {
debug!("Fetching job: {}", &jr.job_id); info!("Fetching job: {}", &jr.job_id);
let fetched_job = loop { let fetched_job = loop {
match client.get_jobs(Some(jr.job_id)).await { match client.get_jobs(Some(jr.job_id)).await {
Ok(mut result) => break result.pop().unwrap(), Ok(mut result) => break result.pop().unwrap(),
@ -35,7 +34,7 @@ pub async fn process_request(job_requests: Vec<AssignedJob>, client: &ClientHand
JobCache::insert(fetched_job); JobCache::insert(fetched_job);
} }
} }
debug!( info!(
"Scheduling jobs: {}", "Scheduling jobs: {}",
job_requests job_requests
.iter() .iter()
@ -46,7 +45,9 @@ pub async fn process_request(job_requests: Vec<AssignedJob>, client: &ClientHand
let mut builder = JobBuilder::from_request(job_requests); let mut builder = JobBuilder::from_request(job_requests);
let errors = builder.pop_errors(); let errors = builder.pop_errors();
if !errors.is_empty() { if !errors.is_empty() {
errors.into_iter().for_each(ErrChan::send) for e in errors {
ErrChan::send(e, "ebld").await;
}
} }
builder.unwrap_one().spawn().await; builder.unwrap_one().spawn().await;
} }
@ -54,8 +55,8 @@ pub async fn process_request(job_requests: Vec<AssignedJob>, client: &ClientHand
async fn error_reporting(client: Arc<ClientHandler>) -> ! { async fn error_reporting(client: Arc<ClientHandler>) -> ! {
loop { loop {
let err = ErrChan::recv(); match ErrChan::recv().await {
debug!("Error encountered: {:?}", err); Some(err) => {
'retry: for _ in 0..3 { 'retry: for _ in 0..3 {
match client.report(&[Reportable::Error(err.to_string())]).await { match client.report(&[Reportable::Error(err.to_string())]).await {
Ok(_) => break 'retry, Ok(_) => break 'retry,
@ -66,20 +67,23 @@ async fn error_reporting(client: Arc<ClientHandler>) -> ! {
} }
} }
} }
None => sleep(Duration::from_secs(3)).await,
}
}
} }
async fn do_stuff(client: Arc<ClientHandler>) -> ! { async fn do_stuff(client: Arc<ClientHandler>) -> ! {
loop { loop {
match client.get_personal_jobs(Some(get_self_uid())).await { match client.get_personal_jobs(get_self_uid()).await {
Ok(resp) => { Ok(resp) => {
process_request(resp, &client).await; process_request(resp, &client).await;
} }
Err(err) => ErrChan::send(err), Err(err) => ErrChan::send(err, "personal").await,
} }
let result: Vec<Reportable> = pop_completed().await.into_iter().collect(); let result: Vec<Reportable> = pop_completed().await.into_iter().collect();
if !result.is_empty() { if !result.is_empty() {
if let Err(err) = client.report(&result).await { if let Err(err) = client.report(&result).await {
ErrChan::send(err) ErrChan::send(err, "report").await;
} }
} }
sleep(Duration::from_secs(ITERATION_LATENCY)).await; sleep(Duration::from_secs(ITERATION_LATENCY)).await;
@ -87,11 +91,12 @@ async fn do_stuff(client: Arc<ClientHandler>) -> ! {
} }
pub async fn run_forever() -> ! { pub async fn run_forever() -> ! {
panic::set_hook(Box::new(|panic_info| { let env = load_env_default().unwrap();
ErrChan::send(UError::Panic(panic_info.to_string())) let client = Arc::new(ClientHandler::new(&env.u_server, None));
})); tokio::spawn(error_reporting(client.clone()));
if cfg!(debug_assertions) { if cfg!(debug_assertions) {
init_logger(format!( init_logger(Some(format!(
"u_agent-{}", "u_agent-{}",
get_self_uid() get_self_uid()
.hyphenated() .hyphenated()
@ -99,14 +104,12 @@ pub async fn run_forever() -> ! {
.split("-") .split("-")
.next() .next()
.unwrap() .unwrap()
)); )));
} else { } else {
if let Err(e) = Daemonize::new().start() { if let Err(e) = Daemonize::new().start() {
ErrChan::send(UError::Runtime(e.to_string())) ErrChan::send(UError::Runtime(e.to_string()), "deeeemon").await
} }
} }
let env = load_env_default().unwrap(); info!("Startup");
let client = Arc::new(ClientHandler::new(&env.u_server, None));
tokio::spawn(error_reporting(client.clone()));
do_stuff(client).await do_stuff(client).await
} }

@ -10,7 +10,6 @@ edition = "2021"
actix-web = "3.3.2" actix-web = "3.3.2"
backtrace = "0.3.61" backtrace = "0.3.61"
structopt = "0.3.21" structopt = "0.3.21"
log = "^0.4"
uuid = "0.6.5" uuid = "0.6.5"
serde_json = "1.0.4" serde_json = "1.0.4"
serde = { version = "1.0.114", features = ["derive"] } serde = { version = "1.0.114", features = ["derive"] }

@ -1,3 +1,4 @@
use anyhow::Result as AnyResult;
use structopt::StructOpt; use structopt::StructOpt;
use u_lib::{ use u_lib::{
api::ClientHandler, api::ClientHandler,
@ -76,10 +77,10 @@ fn parse_uuid(src: &str) -> Result<Uuid, String> {
} }
pub async fn process_cmd(client: ClientHandler, args: Args) -> UResult<String> { pub async fn process_cmd(client: ClientHandler, args: Args) -> UResult<String> {
fn to_json<Msg: AsMsg>(data: UResult<Msg>) -> String { fn to_json<Msg: AsMsg>(data: AnyResult<Msg>) -> String {
let data = match data { let data = match data {
Ok(r) => PanelResult::Ok(r), Ok(r) => PanelResult::Ok(r),
Err(e) => PanelResult::Err(e), Err(e) => PanelResult::Err(e.downcast().expect("unknown error type")),
}; };
serde_json::to_string(&data).unwrap() serde_json::to_string(&data).unwrap()
} }

@ -10,6 +10,7 @@ use argparse::{process_cmd, Args};
use serde::Deserialize; use serde::Deserialize;
use structopt::StructOpt; use structopt::StructOpt;
use u_lib::api::ClientHandler; use u_lib::api::ClientHandler;
use u_lib::logging::init_logger;
use u_lib::utils::{env::default_host, load_env}; use u_lib::utils::{env::default_host, load_env};
#[derive(Deserialize)] #[derive(Deserialize)]
@ -25,6 +26,8 @@ async fn main() -> AnyResult<()> {
let client = ClientHandler::new(&env.u_server, Some(env.admin_auth_token)); let client = ClientHandler::new(&env.u_server, Some(env.admin_auth_token));
let args = Args::from_args(); let args = Args::from_args();
process_cmd(client, args).await?; init_logger(None::<&str>);
let result = process_cmd(client, args).await?;
println!("{result}");
Ok(()) Ok(())
} }

@ -68,7 +68,7 @@ async fn send_cmd(
#[actix_web::main] #[actix_web::main]
pub async fn serve(client: ClientHandler) -> std::io::Result<()> { pub async fn serve(client: ClientHandler) -> std::io::Result<()> {
init_logger("u_panel"); init_logger(Some("u_panel"));
let addr = "127.0.0.1:8080"; let addr = "127.0.0.1:8080";
info!("Serving at http://{}", addr); info!("Serving at http://{}", addr);

@ -5,7 +5,7 @@ name = "u_server"
version = "0.1.0" version = "0.1.0"
[dependencies] [dependencies]
log = "0.4.11" tracing = "0.1.35"
thiserror = "*" thiserror = "*"
warp = { version = "0.3.1", features = ["tls"] } warp = { version = "0.3.1", features = ["tls"] }
uuid = { version = "0.6.5", features = ["serde", "v4"] } uuid = { version = "0.6.5", features = ["serde", "v4"] }

@ -112,7 +112,7 @@ impl Endpoints {
err.agent_id, err.agent_id,
Stripped(&err.msg.as_str()) Stripped(&err.msg.as_str())
); );
UDB::lock_db().report_error(&err)?; //UDB::lock_db().report_error(&err)?;
} }
Reportable::Dummy => (), Reportable::Dummy => (),
} }

@ -1,6 +1,11 @@
use u_server_lib::serve; use u_server_lib::serve;
#[macro_use]
extern crate tracing;
#[tokio::main] #[tokio::main]
async fn main() -> Result<(), String> { async fn main() {
serve().await.map_err(|e| e.to_string()) if let Err(e) = serve().await {
error!("U_SERVER error: {}", e);
}
} }

@ -1,5 +1,5 @@
#[macro_use] #[macro_use]
extern crate log; extern crate tracing;
#[cfg(test)] #[cfg(test)]
#[macro_use] #[macro_use]
@ -131,7 +131,7 @@ pub fn init_endpoints(
.map(ok); .map(ok);
let auth_token = format!("Bearer {auth_token}",).into_boxed_str(); let auth_token = format!("Bearer {auth_token}",).into_boxed_str();
let auth_header = warp::header::exact("authorization", Box::leak(auth_token)); let auth_header = warp::header::exact("Authorization", Box::leak(auth_token));
let auth_zone = (get_agents let auth_zone = (get_agents
.or(get_jobs) .or(get_jobs)
@ -160,7 +160,7 @@ pub fn prefill_jobs() -> SResult<()> {
} }
pub async fn serve() -> SResult<()> { pub async fn serve() -> SResult<()> {
init_logger("u_server"); init_logger(Some("u_server"));
prefill_jobs()?; prefill_jobs()?;
let env = load_env::<ServEnv>().map_err(|e| Error::Other(e.to_string()))?; let env = load_env::<ServEnv>().map_err(|e| Error::Other(e.to_string()))?;

@ -8,7 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
tokio = { version = "1.2.0", features = ["macros", "rt-multi-thread", "process", "time"] } tokio = { version = "1.2.0", features = ["macros", "rt-multi-thread", "process", "time"] }
log = "^0.4" tracing = "0.1.35"
uuid = { version = "0.6.5", features = ["serde", "v4"] } uuid = { version = "0.6.5", features = ["serde", "v4"] }
reqwest = { version = "0.11", features = ["json"] } reqwest = { version = "0.11", features = ["json"] }
serde_json = "1.0" serde_json = "1.0"
@ -20,6 +20,7 @@ once_cell = "1.10.0"
[dependencies.u_lib] [dependencies.u_lib]
path = "../lib/u_lib" path = "../lib/u_lib"
version = "*" version = "*"
features = ["panel"]
[[test]] [[test]]

@ -25,7 +25,7 @@ services:
- ../.env - ../.env
- ../.env.private - ../.env.private
environment: environment:
RUST_LOG: trace RUST_LOG: info
healthcheck: healthcheck:
test: ss -tlpn | grep 63714 test: ss -tlpn | grep 63714
interval: 5s interval: 5s
@ -36,8 +36,8 @@ services:
image: unki/u_db image: unki/u_db
networks: networks:
- u_net - u_net
expose: ports:
- '5432' - 54321:5432
env_file: env_file:
- ../.env - ../.env
- ../.env.private - ../.env.private
@ -58,12 +58,14 @@ services:
networks: networks:
- u_net - u_net
volumes: volumes:
- ../target/x86_64-unknown-linux-musl/${PROFILE:-debug}/u_agent:/u_agent - ../target/x86_64-unknown-linux-musl/${PROFILE:-debug}/u_agent:/unki/u_agent
command: /u_agent u_server - ../logs:/unki/logs:rw
working_dir: /unki
command: /unki/u_agent u_server
env_file: env_file:
- ../.env - ../.env
environment: environment:
RUST_LOG: u_agent=debug RUST_LOG: u_agent=debug,u_lib=debug
depends_on: depends_on:
u_server: u_server:
condition: service_healthy condition: service_healthy
@ -78,6 +80,7 @@ services:
- ../certs:/certs - ../certs:/certs
- ../target/x86_64-unknown-linux-musl/${PROFILE:-debug}/u_panel:/u_panel - ../target/x86_64-unknown-linux-musl/${PROFILE:-debug}/u_panel:/u_panel
- ../lib/u_lib:/lib/u_lib - ../lib/u_lib:/lib/u_lib
- ../logs:/tests/logs:rw
working_dir: working_dir:
/tests/ /tests/
depends_on: depends_on:

@ -18,7 +18,7 @@ pub async fn register_agent() -> RegisteredAgent {
let cli = ClientHandler::new(&ENV.u_server, None); let cli = ClientHandler::new(&ENV.u_server, None);
let agent_uid = Uuid::new_v4(); let agent_uid = Uuid::new_v4();
let resp = cli let resp = cli
.get_personal_jobs(Some(agent_uid)) .get_personal_jobs(agent_uid)
.await .await
.unwrap() .unwrap()
.pop() .pop()

@ -13,11 +13,7 @@ pub struct Panel;
impl Panel { impl Panel {
fn run(args: &[&str]) -> Output { fn run(args: &[&str]) -> Output {
Command::new(PANEL_BINARY) Command::new(PANEL_BINARY).args(args).output().unwrap()
.arg("--json")
.args(args)
.output()
.unwrap()
} }
pub fn output_argv<T: DeserializeOwned>(argv: &[&str]) -> PanelResult<T> { pub fn output_argv<T: DeserializeOwned>(argv: &[&str]) -> PanelResult<T> {

@ -2,6 +2,7 @@ use crate::fixtures::agent::*;
use crate::helpers::Panel; use crate::helpers::Panel;
use rstest::rstest; use rstest::rstest;
use serde_json::{json, to_string};
use std::error::Error; use std::error::Error;
use std::time::Duration; use std::time::Duration;
use tokio::time::sleep; use tokio::time::sleep;
@ -14,7 +15,7 @@ type TestResult<R = ()> = Result<R, Box<dyn Error>>;
#[tokio::test] #[tokio::test]
async fn test_registration(#[future] register_agent: RegisteredAgent) -> TestResult { async fn test_registration(#[future] register_agent: RegisteredAgent) -> TestResult {
let agent = register_agent.await; let agent = register_agent.await;
let agents: Vec<Agent> = Panel::check_output("agents list"); let agents: Vec<Agent> = Panel::check_output("agents read");
let found = agents.iter().find(|v| v.id == agent.uid); let found = agents.iter().find(|v| v.id == agent.uid);
assert!(found.is_some()); assert!(found.is_some());
Panel::check_status(format!("agents delete {}", agent.uid)); Panel::check_status(format!("agents delete {}", agent.uid));
@ -23,17 +24,22 @@ async fn test_registration(#[future] register_agent: RegisteredAgent) -> TestRes
#[tokio::test] #[tokio::test]
async fn test_setup_tasks() -> TestResult { async fn test_setup_tasks() -> TestResult {
//some independent agents should present let agents: Vec<Agent> = Panel::check_output("agents read");
let agents: Vec<Agent> = Panel::check_output("agents list"); let agent_uid = match agents.get(0) {
let agent_uid = agents[0].id; Some(a) => a.id,
None => panic!("Some independent agents should present"),
};
let job_alias = "passwd_contents"; let job_alias = "passwd_contents";
let cmd = format!("jobs add --alias {job_alias} 'cat /etc/passwd'"); let job = json!(
{"alias": job_alias, "payload": b"cat /etc/passwd" }
);
let cmd = format!("jobs create '{}'", to_string(&job).unwrap());
Panel::check_status(cmd); Panel::check_status(cmd);
let cmd = format!("map add {} {}", agent_uid, job_alias); let cmd = format!("map create {} {}", agent_uid, job_alias);
let assigned_uids: Vec<Uuid> = Panel::check_output(cmd); let assigned_uids: Vec<Uuid> = Panel::check_output(cmd);
for _ in 0..3 { for _ in 0..3 {
let result: Vec<AssignedJob> = let result: Vec<AssignedJob> =
Panel::check_output(format!("map list {}", assigned_uids[0])); Panel::check_output(format!("map read {}", assigned_uids[0]));
if result[0].state == JobState::Finished { if result[0].state == JobState::Finished {
return Ok(()); return Ok(());
} else { } else {

@ -15,7 +15,6 @@ libc = "^0.2"
lazy_static = "1.4.0" lazy_static = "1.4.0"
futures = "0.3.5" futures = "0.3.5"
thiserror = "*" thiserror = "*"
log = "*"
diesel-derive-enum = { version = "1", features = ["postgres"] } diesel-derive-enum = { version = "1", features = ["postgres"] }
chrono = "0.4.19" chrono = "0.4.19"
strum = { version = "0.20", features = ["derive"] } strum = { version = "0.20", features = ["derive"] }
@ -27,6 +26,8 @@ envy = "0.4.2"
serde_json = "1.0.81" serde_json = "1.0.81"
tracing-subscriber = { version = "0.3.14", features = ["env-filter"] } tracing-subscriber = { version = "0.3.14", features = ["env-filter"] }
tracing-appender = "0.2.2" tracing-appender = "0.2.2"
log = "*"
anyhow = "1.0.58"
[features] [features]
panel = [] panel = []

@ -1,20 +1,23 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::Debug;
use crate::{ use crate::{
config::MASTER_PORT, config::MASTER_PORT,
messaging::{self, AsMsg, BaseMessage, Empty}, messaging::{self, AsMsg, BaseMessage, Empty},
models::{self, Agent}, models::{self},
utils::opt_to_string, utils::opt_to_string,
UError, UResult, UError,
}; };
use anyhow::{Context, Result};
use reqwest::{header::HeaderMap, Certificate, Client, Identity, Url}; use reqwest::{header::HeaderMap, Certificate, Client, Identity, Url};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde_json::from_str;
use uuid::Uuid; use uuid::Uuid;
const AGENT_IDENTITY: &[u8] = include_bytes!("../../../certs/alice.p12"); const AGENT_IDENTITY: &[u8] = include_bytes!("../../../certs/alice.p12");
const ROOT_CA_CERT: &[u8] = include_bytes!("../../../certs/ca.crt"); const ROOT_CA_CERT: &[u8] = include_bytes!("../../../certs/ca.crt");
#[derive(Clone)] #[derive(Clone, Debug)]
pub struct ClientHandler { pub struct ClientHandler {
base_url: Url, base_url: Url,
client: Client, client: Client,
@ -43,50 +46,50 @@ impl ClientHandler {
} }
} }
async fn _req<P: AsMsg, M: AsMsg + DeserializeOwned>( async fn _req<P: AsMsg + Debug, M: AsMsg + DeserializeOwned + Debug + Default>(
&self, &self,
url: impl AsRef<str>, url: impl AsRef<str> + Debug,
payload: P, payload: P,
) -> UResult<M> { ) -> Result<M> {
let request = self let request = self
.client .client
.post(self.base_url.join(url.as_ref()).unwrap()) .post(self.base_url.join(url.as_ref()).unwrap())
.json(&payload.as_message()); .json(&payload.as_message());
let response = request.send().await?; let response = request.send().await.context("send")?;
let content_len = response.content_length();
let is_success = match response.error_for_status_ref() { let is_success = match response.error_for_status_ref() {
Ok(_) => Ok(()), Ok(_) => Ok(()),
Err(e) => Err(UError::from(e)), Err(e) => Err(UError::from(e)),
}; };
let resp = response.text().await?; let resp = response.text().await.context("resp")?;
debug!("url = {}, resp = {}", url.as_ref(), resp);
match is_success { match is_success {
Ok(_) => serde_json::from_str::<BaseMessage<M>>(&resp) Ok(_) => from_str::<BaseMessage<M>>(&resp)
.map(|msg| msg.into_inner()) .map(|msg| msg.into_inner())
.or_else(|e| Err(UError::NetError(e.to_string(), resp.clone()))), .or_else(|e| match content_len {
Some(0) => Ok(Default::default()),
_ => Err(UError::NetError(e.to_string(), resp)),
}),
Err(UError::NetError(err, _)) => Err(UError::NetError(err, resp)), Err(UError::NetError(err, _)) => Err(UError::NetError(err, resp)),
_ => unreachable!(), _ => unreachable!(),
} }
.map_err(From::from)
} }
// get jobs for client // get jobs for client
pub async fn get_personal_jobs( pub async fn get_personal_jobs(&self, url_param: Uuid) -> Result<Vec<models::AssignedJob>> {
&self, self._req(format!("get_personal_jobs/{}", url_param), Empty)
url_param: Option<Uuid>,
) -> UResult<Vec<models::AssignedJob>> {
self._req(
format!("get_personal_jobs/{}", opt_to_string(url_param)),
Empty,
)
.await .await
} }
// send something to server // send something to server
pub async fn report(&self, payload: &[messaging::Reportable]) -> UResult<Empty> { pub async fn report(&self, payload: &[messaging::Reportable]) -> Result<Empty> {
self._req("report", payload).await self._req("report", payload).await
} }
// download file // download file
pub async fn dl(&self, file: String) -> UResult<Vec<u8>> { pub async fn dl(&self, file: String) -> Result<Vec<u8>> {
self._req(format!("dl/{file}"), Empty).await self._req(format!("dl/{file}"), Empty).await
} }
} }
@ -95,40 +98,40 @@ impl ClientHandler {
#[cfg(feature = "panel")] #[cfg(feature = "panel")]
impl ClientHandler { impl ClientHandler {
/// agent listing /// agent listing
pub async fn get_agents(&self, agent: Option<Uuid>) -> UResult<Vec<models::Agent>> { pub async fn get_agents(&self, agent: Option<Uuid>) -> Result<Vec<models::Agent>> {
self._req(format!("get_agents/{}", opt_to_string(agent)), Empty) self._req(format!("get_agents/{}", opt_to_string(agent)), Empty)
.await .await
} }
/// update something /// update something
pub async fn update_item(&self, item: impl AsMsg) -> UResult<Empty> { pub async fn update_item(&self, item: impl AsMsg + Debug) -> Result<Empty> {
self._req("update_item", item).await self._req("update_item", item).await
} }
/// get all available jobs /// get all available jobs
pub async fn get_jobs(&self, job: Option<Uuid>) -> UResult<Vec<models::JobMeta>> { pub async fn get_jobs(&self, job: Option<Uuid>) -> Result<Vec<models::JobMeta>> {
self._req(format!("get_jobs/{}", opt_to_string(job)), Empty) self._req(format!("get_jobs/{}", opt_to_string(job)), Empty)
.await .await
} }
/// create and upload job /// create and upload job
pub async fn upload_jobs(&self, payload: &[models::JobMeta]) -> UResult<Empty> { pub async fn upload_jobs(&self, payload: &[models::JobMeta]) -> Result<Empty> {
self._req("upload_jobs", payload).await self._req("upload_jobs", payload).await
} }
/// delete something /// delete something
pub async fn del(&self, item: Uuid) -> UResult<i32> { pub async fn del(&self, item: Uuid) -> Result<i32> {
self._req(format!("del/{item}"), Empty).await self._req(format!("del/{item}"), Empty).await
} }
/// set jobs for any agent /// set jobs for any agent
pub async fn set_jobs(&self, agent: Uuid, job_idents: &[String]) -> UResult<Vec<Uuid>> { pub async fn set_jobs(&self, agent: Uuid, job_idents: &[String]) -> Result<Vec<Uuid>> {
self._req(format!("set_jobs/{agent}"), job_idents).await self._req(format!("set_jobs/{agent}"), job_idents).await
} }
/// get jobs for any agent /// get jobs for any agent
pub async fn get_agent_jobs(&self, agent: Option<Uuid>) -> UResult<Vec<models::AssignedJob>> { pub async fn get_agent_jobs(&self, agent: Option<Uuid>) -> Result<Vec<models::AssignedJob>> {
self._req(format!("set_jobs/{}", opt_to_string(agent)), Empty) self._req(format!("get_personal_jobs/{}", opt_to_string(agent)), Empty)
.await .await
} }
} }

@ -17,7 +17,7 @@ impl JobBuilder {
pub fn from_request(job_requests: impl OneOrVec<AssignedJob>) -> CombinedResult<Self> { pub fn from_request(job_requests: impl OneOrVec<AssignedJob>) -> CombinedResult<Self> {
let job_requests = job_requests.into_vec(); let job_requests = job_requests.into_vec();
let mut prepared: Vec<DynFut> = vec![]; let mut prepared: Vec<DynFut> = vec![];
let mut result = CombinedResult::<JobBuilder, UError>::new(); let mut result = CombinedResult::<JobBuilder, _>::new();
for req in job_requests { for req in job_requests {
let job_meta = JobCache::get(req.job_id); let job_meta = JobCache::get(req.job_id);
if job_meta.is_none() { if job_meta.is_none() {

@ -1,9 +1,10 @@
use crate::UError; use anyhow::Error;
use crossbeam::channel::{self, Receiver, Sender};
use once_cell::sync::OnceCell; use once_cell::sync::OnceCell;
use tokio::sync::mpsc::{channel, error::TryRecvError, Receiver, Sender};
use tokio::sync::{Mutex, MutexGuard};
type ChanError = UError; type ChanError = Error;
static ERR_CHAN: OnceCell<ErrChan> = OnceCell::new(); static ERR_CHAN: OnceCell<Mutex<ErrChan>> = OnceCell::new();
pub struct ErrChan { pub struct ErrChan {
tx: Sender<ChanError>, tx: Sender<ChanError>,
@ -11,18 +12,27 @@ pub struct ErrChan {
} }
impl ErrChan { impl ErrChan {
fn get() -> &'static Self { async fn get() -> MutexGuard<'static, Self> {
ERR_CHAN.get_or_init(|| { ERR_CHAN
let (tx, rx) = channel::bounded(20); .get_or_init(|| {
Self { tx, rx } let (tx, rx) = channel(20);
Mutex::new(Self { tx, rx })
}) })
.lock()
.await
} }
pub fn send(msg: ChanError) { pub async fn send(err: impl Into<ChanError>, ctx: impl AsRef<str>) {
Self::get().tx.send(msg).unwrap() let err = err.into();
error!("Encountered an error at '{}': {:?}", ctx.as_ref(), err);
Self::get().await.tx.try_send(err).unwrap();
} }
pub fn recv() -> ChanError { pub async fn recv() -> Option<ChanError> {
Self::get().rx.recv().unwrap() match Self::get().await.rx.try_recv() {
Ok(r) => Some(r),
Err(TryRecvError::Disconnected) => panic!("err chan disconnected"),
Err(TryRecvError::Empty) => None,
}
} }
} }

@ -2,15 +2,26 @@ use std::env;
use std::path::Path; use std::path::Path;
use tracing_appender::rolling; use tracing_appender::rolling;
use tracing_subscriber::EnvFilter; use tracing_subscriber::{fmt, prelude::*, registry, EnvFilter};
pub fn init_logger(logfile: impl AsRef<Path> + Send + Sync + 'static) { pub fn init_logger(logfile: Option<impl AsRef<Path> + Send + Sync + 'static>) {
if env::var("RUST_LOG").is_err() { if env::var("RUST_LOG").is_err() {
env::set_var("RUST_LOG", "info") env::set_var("RUST_LOG", "info")
} }
tracing_subscriber::fmt::fmt() let reg = registry()
.with_env_filter(EnvFilter::from_default_env()) .with(EnvFilter::from_default_env())
.with_writer(move || rolling::never(".", logfile.as_ref().with_extension("log"))) .with(fmt::layer());
.init(); match logfile {
Some(file) => reg
.with(
fmt::layer()
.with_writer(move || {
rolling::never("logs", file.as_ref().with_extension("log"))
})
.with_ansi(false),
)
.init(),
None => reg.init(),
};
} }

@ -28,7 +28,7 @@ impl fmt::Display for Empty {
} }
} }
#[derive(Serialize, Deserialize, Clone, PartialEq)] #[derive(Serialize, Deserialize, Clone, PartialEq, Debug)]
pub enum Reportable { pub enum Reportable {
Assigned(AssignedJob), Assigned(AssignedJob),
Agent(Agent), Agent(Agent),

@ -25,13 +25,20 @@ pub struct JobMeta {
#[derive(Deserialize)] #[derive(Deserialize)]
pub struct RawJobMeta { pub struct RawJobMeta {
#[serde(default)]
pub alias: Option<String>, pub alias: Option<String>,
#[serde(default)]
pub argv: String, pub argv: String,
#[serde(default)]
pub id: Uuid, pub id: Uuid,
#[serde(default)]
pub exec_type: JobType, pub exec_type: JobType,
//pub schedule: JobSchedule, //pub schedule: JobSchedule,
#[serde(default)]
pub platform: String, pub platform: String,
#[serde(default)]
pub payload: Option<Vec<u8>>, pub payload: Option<Vec<u8>>,
#[serde(default)]
pub payload_path: Option<PathBuf>, pub payload_path: Option<PathBuf>,
} }

@ -1,9 +1,9 @@
use std::fmt::Debug; use std::fmt::Debug;
use crate::utils::OneOrVec; use crate::utils::OneOrVec;
use crate::UError; use anyhow::Error;
pub struct CombinedResult<T, E: Debug = UError> { pub struct CombinedResult<T, E: Debug = Error> {
ok: Vec<T>, ok: Vec<T>,
err: Vec<E>, err: Vec<E>,
} }
@ -20,8 +20,13 @@ impl<T, E: Debug> CombinedResult<T, E> {
self.ok.extend(result.into_vec()); self.ok.extend(result.into_vec());
} }
pub fn err(&mut self, err: impl OneOrVec<E>) { pub fn err<I: Into<E>>(&mut self, err: impl OneOrVec<I>) {
self.err.extend(err.into_vec()); self.err.extend(
err.into_vec()
.into_iter()
.map(Into::into)
.collect::<Vec<_>>(),
);
} }
pub fn unwrap(self) -> Vec<T> { pub fn unwrap(self) -> Vec<T> {

@ -3,7 +3,7 @@ use std::iter::Iterator;
use std::slice::Iter as SliceIter; use std::slice::Iter as SliceIter;
use std::str::Chars; use std::str::Chars;
const MAX_DATA_LEN: usize = 200; const MAX_DATA_LEN: usize = 2000;
pub trait Strippable { pub trait Strippable {
type Item: fmt::Display; type Item: fmt::Display;

@ -108,7 +108,7 @@ impl ProcOutput {
altered = true; altered = true;
} }
if !altered { if !altered {
result.extend(b"No data"); result.extend(b"<empty output>");
} }
result result
} }

@ -4,7 +4,7 @@ CREATE TYPE JobState AS ENUM ('queued', 'running', 'finished');
CREATE TYPE AgentState AS ENUM ('new', 'active', 'banned'); CREATE TYPE AgentState AS ENUM ('new', 'active', 'banned');
CREATE TABLE IF NOT EXISTS agents ( CREATE TABLE IF NOT EXISTS agents (
alias TEXT UNIQUE alias TEXT
, hostname TEXT NOT NULL , hostname TEXT NOT NULL
, id UUID NOT NULL DEFAULT uuid_generate_v4() , id UUID NOT NULL DEFAULT uuid_generate_v4()
, is_root BOOLEAN NOT NULL DEFAULT false , is_root BOOLEAN NOT NULL DEFAULT false
@ -34,11 +34,9 @@ CREATE TABLE IF NOT EXISTS ip_addrs (
); );
CREATE TABLE IF NOT EXISTS jobs ( CREATE TABLE IF NOT EXISTS jobs (
alias TEXT UNIQUE alias TEXT
, argv TEXT NOT NULL , argv TEXT NOT NULL
, id UUID NOT NULL DEFAULT uuid_generate_v4() , id UUID NOT NULL DEFAULT uuid_generate_v4()
-- Shell, Binary (with program download),
-- Python (with program and python download if not exist), Management
, exec_type JobType NOT NULL DEFAULT 'shell' , exec_type JobType NOT NULL DEFAULT 'shell'
, platform TEXT NOT NULL , platform TEXT NOT NULL
, payload BYTEA , payload BYTEA
@ -47,7 +45,7 @@ CREATE TABLE IF NOT EXISTS jobs (
CREATE TABLE IF NOT EXISTS results ( CREATE TABLE IF NOT EXISTS results (
agent_id UUID NOT NULL agent_id UUID NOT NULL
, alias TEXT UNIQUE , alias TEXT
, created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP , created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
, id UUID NOT NULL DEFAULT uuid_generate_v4() , id UUID NOT NULL DEFAULT uuid_generate_v4()
, job_id UUID NOT NULL , job_id UUID NOT NULL

Loading…
Cancel
Save