diff --git a/Cargo.lock b/Cargo.lock index bfc232c..ea882c2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -124,7 +124,7 @@ checksum = "90eea657cc8028447cbda5068f4e10c4fadba0131624f4f7dd1a9c46ffc8d81f" dependencies = [ "assert_matches", "aya-obj", - "bitflags", + "bitflags 2.4.1", "bytes", "lazy_static", "libc", @@ -193,7 +193,7 @@ version = "0.69.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4c69fae65a523209d34240b60abe0c42d33d1045d445c0839d8a4894a736e2d" dependencies = [ - "bitflags", + "bitflags 2.4.1", "cexpr", "clang-sys", "lazy_static", @@ -210,6 +210,12 @@ dependencies = [ "which", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitflags" version = "2.4.1" @@ -509,6 +515,24 @@ dependencies = [ "libc", ] +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "firo" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a45ffbf801908139f8cd2a9069f6b86061761b6f41a1d6c2955b939bf651026" +dependencies = [ + "flate2", + "huby", + "tempfile", + "thiserror", +] + [[package]] name = "flate2" version = "1.0.25" @@ -699,6 +723,16 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +[[package]] +name = "huby" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7b227425d1ceea9ffa6f8cf4c11d001c3982076a7c0ff6b094af53be628d9d0" +dependencies = [ + "serde", + "thiserror", +] + [[package]] name = "humantime" version = "2.1.0" @@ -788,10 +822,12 @@ dependencies = [ "chrono", "clap", "env_logger", + "firo", "futures", "gene", "gene_derive", "hex", + "huby", "ip_network", "kunai-common", "kunai-macros", @@ -1114,7 +1150,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "731e0d9356b0c25f16f33b5be79b1c57b562f141ebfcdb0ad8ac2c13a24293b4" dependencies = [ - "bitflags", + "bitflags 2.4.1", "chrono", "flate2", "hex", @@ -1129,7 +1165,7 @@ version = "0.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2d3554923a69f4ce04c4a754260c338f505ce22642d3830e049a399fc2059a29" dependencies = [ - "bitflags", + "bitflags 2.4.1", "chrono", "hex", ] @@ -1149,6 +1185,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" +dependencies = [ + "bitflags 1.3.2", +] + [[package]] name = "regex" version = "1.10.2" @@ -1190,7 +1235,7 @@ version = "0.38.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b426b0506e5d50a7d8dafcf2e81471400deb602392c7dd110815afb4eaf02a3" dependencies = [ - "bitflags", + "bitflags 2.4.1", "errno", "libc", "linux-raw-sys", @@ -1375,6 +1420,19 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef1adac450ad7f4b3c28589471ade84f25f731a7a0fe30d71dfa9f60fd808e5" +dependencies = [ + "cfg-if", + "fastrand", + "redox_syscall", + "rustix", + "windows-sys 0.48.0", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -1386,18 +1444,18 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "03468839009160513471e86a034bb2c5c0e4baae3b43f79ffc55c4a5427b3297" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.58" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c61f3ba182994efc43764a46c018c347bc492c79f024e705f46567b418f6d4f7" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", diff --git a/kunai/Cargo.toml b/kunai/Cargo.toml index 0237fff..d29b8ef 100644 --- a/kunai/Cargo.toml +++ b/kunai/Cargo.toml @@ -56,6 +56,9 @@ lazy_static = "1.4.0" serde_json = "1.0.108" uuid = { version = "1.6.1", features = ["serde", "v5"] } object = { version = "0.34.0", features = ["elf"] } +huby = { version = "0.1", features = ["serde"] } +firo = { version = "0.1" } + [[bin]] name = "kunai" diff --git a/kunai/src/bin/main.rs b/kunai/src/bin/main.rs index cad5c8a..3fb0e7c 100644 --- a/kunai/src/bin/main.rs +++ b/kunai/src/bin/main.rs @@ -32,10 +32,11 @@ use tokio::sync::mpsc::error::SendError; use std::borrow::Cow; use std::collections::{HashMap, HashSet, VecDeque}; -use std::fs::{self, File}; +use std::fs::{self, DirBuilder, File}; use std::io::{self, BufRead, Write}; use std::net::IpAddr; +use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt}; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -125,6 +126,49 @@ impl SystemInfo { } } +pub enum Output { + Stdout(std::io::Stdout), + Stderr(std::io::Stderr), + // variant too big, boxing suggested by clippy + File(Box), +} + +impl Output { + #[inline(always)] + fn stdout() -> Self { + Self::Stdout(std::io::stdout()) + } + + #[inline(always)] + fn stderr() -> Self { + Self::Stderr(std::io::stderr()) + } +} + +impl From for Output { + fn from(value: firo::File) -> Self { + Self::File(Box::new(value)) + } +} + +impl io::Write for Output { + fn write(&mut self, buf: &[u8]) -> io::Result { + match self { + Self::Stdout(o) => o.write(buf), + Self::Stderr(o) => o.write(buf), + Self::File(o) => o.write(buf), + } + } + + fn flush(&mut self) -> io::Result<()> { + match self { + Self::Stdout(o) => o.flush(), + Self::Stderr(o) => o.flush(), + Self::File(o) => o.flush(), + } + } +} + struct EventConsumer { system_info: SystemInfo, engine: gene::Engine, @@ -133,17 +177,51 @@ struct EventConsumer { cache: cache::Cache, tasks: HashMap, resolved: HashMap, - output: std::fs::File, + output: Output, } impl EventConsumer { - pub fn with_config(config: Config) -> anyhow::Result { + fn prepare_output(config: &Config) -> anyhow::Result { let output = match &config.output.as_str() { &"stdout" => String::from("/dev/stdout"), &"stderr" => String::from("/dev/stderr"), v => v.to_string(), }; + let out = match output.as_str() { + "/dev/stdout" => Output::stdout(), + "/dev/stderr" => Output::stderr(), + v => { + let path = PathBuf::from(v); + + if let Some(parent) = path.parent() { + if !parent.exists() { + // we only create parent directory + DirBuilder::new().mode(0o700).create(parent).map_err(|e| { + anyhow!("failed to create output directory {parent:?}: {e}") + })?; + } + } + + match config.output_settings.as_ref() { + Some(s) => firo::OpenOptions::new() + .mode(0o600) + .max_size(s.max_size) + .trigger(s.rotate_size.into()) + .compression(firo::Compression::Gzip) + .create_append(v)? + .into(), + None => firo::OpenOptions::new() + .mode(0o600) + .create_append(v)? + .into(), + } + } + }; + Ok(out) + } + + pub fn with_config(config: Config) -> anyhow::Result { // building up system information let system_info = SystemInfo::from_sys()?.with_host_uuid( config @@ -159,10 +237,7 @@ impl EventConsumer { cache: Cache::with_max_entries(10000), tasks: HashMap::new(), resolved: HashMap::new(), - output: std::fs::OpenOptions::new() - .append(true) - .create(true) - .open(output)?, + output: Self::prepare_output(&config)?, }; // loading rules in the engine @@ -1786,7 +1861,6 @@ fn load_and_attach_bpf(kernel: KernelVersion, bpf: &mut Bpf) -> anyhow::Result

, pub output: String, + pub output_settings: Option, pub max_buffered_events: u16, pub send_data_min_len: Option, pub rules: Vec, @@ -68,6 +76,7 @@ impl Default for Config { Self { host_uuid: None, output: "/dev/stdout".into(), + output_settings: None, max_buffered_events: DEFAULT_MAX_BUFFERED_EVENTS, send_data_min_len: None, rules: vec![],