Skip to content

Commit

Permalink
Showing 11 changed files with 209 additions and 20 deletions.
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions kunai-common/src/bpf_events.rs
Original file line number Diff line number Diff line change
@@ -134,6 +134,11 @@ pub enum Type {
#[str("end_configurable")]
EndConfigurable = 1000,

// Those events are not configurable but should have
// fix event number as these are displayed
#[str("start")]
Start,

// specific events
#[str("task_sched")]
TaskSched,
3 changes: 3 additions & 0 deletions kunai-common/src/bpf_events/events.rs
Original file line number Diff line number Diff line change
@@ -42,6 +42,8 @@ pub mod error;
pub use error::*;
mod loss;
pub use loss::*;
mod status;
pub use status::*;

// prevent using correlation event in bpf code
not_bpf_target_code! {
@@ -90,6 +92,7 @@ const fn max_bpf_event_size() -> usize {
Type::FileRename => FileRenameEvent::size_of(),
Type::FileUnlink => UnlinkEvent::size_of(),
Type::Log => LogEvent::size_of(),
Type::Start => StatusEvent::size_of(),
Type::Loss => LossEvent::size_of(),
Type::Error => ErrorEvent::size_of(),
Type::SyscoreResume => SysCoreResumeEvent::size_of(),
5 changes: 5 additions & 0 deletions kunai-common/src/bpf_events/events/status.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
use crate::bpf_events::Event;

/// Event that must be used only in userland
/// to encode status information
pub type StatusEvent = Event<()>;
6 changes: 3 additions & 3 deletions kunai/Cargo.toml
Original file line number Diff line number Diff line change
@@ -22,16 +22,16 @@ hex = "0.4.3"
md-5 = "0.10.5"
sha1 = "0.10.5"
sha2 = "0.10.6"
chrono = { version = "0.4.24", features = ["clock"] }
chrono = { version = "0.4.24", features = ["clock", "serde"] }
libc = "0.2"
thiserror = "1.0"
procfs = "0.16"
ip_network = "0.4"
lru-st = { version = "0.2", features = ["sync"] }

# detection engine for events
gene = { version = "0.4.0" }
gene_derive = { version = "0.4.0" }
gene = { version = "0.4" }
gene_derive = { version = "0.4" }

anyhow = "1.0.68"
env_logger = "0.10"
72 changes: 61 additions & 11 deletions kunai/src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -13,6 +13,7 @@ use gene::rules::MAX_SEVERITY;
use gene::{Compiler, Engine};
use huby::ByteSize;
use kunai::containers::Container;
use kunai::events::StartData;
use kunai::events::{
agent::AgentEventInfo, BpfProgLoadData, BpfProgTypeInfo, BpfSocketFilterData, CloneData,
ConnectData, DnsQueryData, ErrorData, EventInfo, ExecveData, ExitData, FileData,
@@ -41,6 +42,7 @@ use serde::{Deserialize, Serialize};

use tokio::sync::mpsc::error::SendError;
use tokio::time::timeout;
use uptime::Uptime;

use std::borrow::Cow;
use std::cmp::max;
@@ -307,7 +309,7 @@ impl EventConsumer<'_> {
Ok(out)
}

pub fn with_config(config: Config) -> anyhow::Result<Self> {
pub fn with_config(mut config: Config) -> anyhow::Result<Self> {
// building up system information
let system_info = SystemInfo::from_sys()?.with_host_uuid(
config
@@ -1394,6 +1396,36 @@ impl EventConsumer<'_> {
UserEvent::new(data, info)
}

#[inline(always)]
fn start_event(&self, info: StdEventInfo) -> UserEvent<StartData> {
let mut data = StartData::new();

// setting kunai related info
data.kunai.version = env!("CARGO_PKG_VERSION").into();
let self_exe = PathBuf::from("/proc/self/exe");
data.kunai.exe = Hashes::from_path_ref(self_exe.clone().canonicalize().unwrap_or(self_exe));

data.kunai.config.sha256 = self.config.sha256().ok().unwrap_or("?".into());

// setting up uptime and boottime
if let Ok(uptime) = Uptime::from_sys().inspect_err(|e| error!("failed to get uptime: {e}"))
{
data.system.uptime = Some(uptime.as_secs());
data.system.boot_time = uptime.boot_time().ok();
}

// setting utsname info
if let Ok(uts) = Utsname::from_sys() {
data.system.sysname = uts.sysname().unwrap_or("?".into()).into();
data.system.release = uts.release().unwrap_or("?".into()).into();
data.system.version = uts.version().unwrap_or("?".into()).into();
data.system.machine = uts.machine().unwrap_or("?".into()).into();
data.system.domainname = uts.domainname().unwrap_or("?".into()).into();
}

UserEvent::new(data, info)
}

#[inline(always)]
fn loss_event(&self, info: StdEventInfo, event: &bpf_events::LossEvent) -> UserEvent<LossData> {
UserEvent::new(LossData::from(&event.data), info)
@@ -2154,6 +2186,11 @@ impl EventConsumer<'_> {
panic!("log events should be processed earlier")
}

Type::Start => {
let mut se = self.start_event(std_info);
self.serialize_print(&mut se);
}

Type::Loss => match event!(enc_event) {
Ok(e) => {
let mut evt = self.loss_event(std_info, e);
@@ -2309,8 +2346,10 @@ impl EventProducer {
self.sender.send(EncodedEvent::from_event(event)).await
}

/// Set event batch number then pipe event
#[inline(always)]
fn pipe_event<T>(&mut self, event: Event<T>) {
fn pipe_event<T>(&mut self, mut event: Event<T>) {
event.batch(self.batch);
self.pipe.push_back(EncodedEvent::from_event(event));
}

@@ -2412,11 +2451,11 @@ impl EventProducer {
// we choose what task will handle the reduce process (handle piped events)
let leader_cpu_id = online_cpus[0];
let config = self.config.clone();

let shared = Arc::new(Mutex::new(self));

let event_producer = shared.clone();

// we spawn a task processing events waiting in the pipe
let t = task::spawn(async move {
loop {
let c = event_producer.lock().await.process_piped_events().await?;
@@ -2490,7 +2529,6 @@ impl EventProducer {
ep.stats.update(events.read as u64, events.lost as u64);
// borrow stats
let stats = &ep.stats;
let batch = ep.batch;

// only show error in leader cpu if needed
if cpu_id == leader_cpu_id && stats.lost > last_lost_cnt {
@@ -2522,7 +2560,7 @@ impl EventProducer {
let lost = stats.lost;

// we pipe a data loss event to bubble up info in kunai logs
if let Ok(mut loss_evt) = ep
if let Ok(loss_evt) = ep
.agent_evt_info
.new_event_with_data(
Type::Loss,
@@ -2536,7 +2574,6 @@ impl EventProducer {
error!("failed to create data loss event: {e}")
})
{
loss_evt.batch(batch);
// we pipe data loss event
ep.pipe_event(loss_evt);
}
@@ -3066,6 +3103,7 @@ impl Command {
| Type::EndConfigurable
| Type::TaskSched
| Type::SyscoreResume
| Type::Start
| Type::Loss
| Type::Max => {}
}
@@ -3183,10 +3221,19 @@ impl Command {
info!("Starting event producer");
// we start producer
let mut bpf = kunai::prepare_bpf(current_kernel, &conf, vll)?;
let arc_prod =
EventProducer::with_params(&mut bpf, conf.clone(), sender.clone())?
.produce()
.await;
let mut prod =
EventProducer::with_params(&mut bpf, conf.clone(), sender.clone())?;

// we create and pipe a start event
if let Ok(start) = prod
.agent_evt_info
.new_event_with_data(Type::Start, ())
.inspect_err(|e| error!("failed at generating start event: {e}"))
{
prod.pipe_event(start);
}

let arc_prod = prod.produce().await;

// we load and attach bpf programs
kunai::load_and_attach_bpf(&conf, current_kernel, &mut bpf)?;
@@ -3290,7 +3337,10 @@ impl Command {
fn config(co: ConfigOpt) -> anyhow::Result<()> {
if co.dump {
let conf = Config::default().generate_host_uuid();
println!("{}", serde_yaml::to_string(&conf)?);
// do not use println because to_string already includes a
// trailing newline. Using print! allow one to easily compute
// config hash as the one found in start event.
print!("{}", serde_yaml::to_string(&conf)?);
return Ok(());
}

15 changes: 13 additions & 2 deletions kunai/src/config.rs
Original file line number Diff line number Diff line change
@@ -11,6 +11,8 @@ use std::{
};
use thiserror::Error;

use crate::util::sha256_data;

pub const DEFAULT_SEND_DATA_MIN_LEN: u64 = 256;
pub const DEFAULT_MAX_BUFFERED_EVENTS: u16 = 1024;

@@ -139,9 +141,12 @@ impl Config {
}
}

pub fn host_uuid(&self) -> Option<uuid::Uuid> {
pub fn host_uuid(&mut self) -> Option<uuid::Uuid> {
// host_uuid in config supersedes system host_uuid
self.host_uuid.or(host_uuid())
self.host_uuid.or(host_uuid()).and_then(|u| {
self.host_uuid = Some(u);
self.host_uuid
})
}

pub fn harden(mut self, value: bool) -> Self {
@@ -176,6 +181,12 @@ impl Config {
pub fn disable_all(&mut self) {
self.events.iter_mut().for_each(|(_, e)| e.disable())
}

/// Serialize the configuration in yaml then
/// computes the sha256 of it
pub fn sha256(&self) -> Result<String, serde_yaml::Error> {
serde_yaml::to_string(self).map(sha256_data)
}
}

impl TryFrom<Config> for Filter {
2 changes: 2 additions & 0 deletions kunai/src/events.rs
Original file line number Diff line number Diff line change
@@ -23,6 +23,8 @@ use crate::{
};

pub mod agent;
mod start;
pub use start::*;

#[derive(Debug, Default, Serialize, Deserialize, FieldGetter)]
pub struct File {
52 changes: 52 additions & 0 deletions kunai/src/events/start.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};

use crate::cache::Hashes;

/// System related information
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct SystemData {
/// Uptime in seconds (read from /proc/uptime)
pub uptime: Option<f64>,
/// Boot time computed from uptime
pub boot_time: Option<DateTime<Utc>>,
/// Utsname information, except nodename
/// which duplicates information in
/// .info.host.name
pub sysname: String,
pub release: String,
pub version: String,
pub machine: String,
pub domainname: String,
}

/// System related information
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct ConfigData {
pub sha256: String,
}

/// Encodes Kunai related data
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct KunaiData {
/// Version
pub version: String,
/// Information about executable
pub exe: Hashes,
/// Configuration related data
pub config: ConfigData,
}

/// Structure holding information we want
/// to display in start events
#[derive(Default, Debug, Serialize, Deserialize)]
pub struct StartData {
pub system: SystemData,
pub kunai: KunaiData,
}

impl StartData {
pub fn new() -> Self {
Default::default()
}
}
1 change: 1 addition & 0 deletions kunai/src/util.rs
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ pub mod bpf;
pub mod elf;
pub mod namespace;
pub mod uname;
pub mod uptime;

#[inline]
pub fn is_public_ip(ip: IpAddr) -> bool {
59 changes: 59 additions & 0 deletions kunai/src/util/uptime.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
use std::{
fs, io,
num::ParseFloatError,
time::{Duration, TryFromFloatSecsError},
};

use chrono::{OutOfRangeError, Utc};
use thiserror::Error;

#[derive(Debug, Error)]
pub enum Error {
#[error("failed to read uptime")]
Read,
#[error("parse: {0}")]
ParseFloat(#[from] ParseFloatError),
#[error("io: {0}")]
Io(#[from] io::Error),
#[error("try_from: {0}")]
TryFromFloatSecs(#[from] TryFromFloatSecsError),
#[error("oor: {0}")]
OutOfRange(#[from] OutOfRangeError),
#[error("out of range date computation")]
ComputeOutOfRange,
}

#[derive(Debug)]
pub struct Uptime(f64, chrono::Duration);

impl Uptime {
#[inline]
pub fn from_sys() -> Result<Self, Error> {
// Read the content of /proc/uptime
let uptime_content = fs::read_to_string("/proc/uptime")?;

// Extract the uptime in seconds
let uptime_seconds: f64 = uptime_content
.split_whitespace()
.next()
.ok_or(Error::Read)?
.parse()?;

Ok(Self(
uptime_seconds,
chrono::Duration::from_std(Duration::try_from_secs_f64(uptime_seconds)?)?,
))
}

#[inline(always)]
pub fn as_secs(&self) -> f64 {
self.0
}

#[inline(always)]
pub fn boot_time(&self) -> Result<chrono::DateTime<Utc>, Error> {
Utc::now()
.checked_sub_signed(self.1)
.ok_or(Error::ComputeOutOfRange)
}
}

0 comments on commit 351d3a8

Please sign in to comment.