API Reference#
This section contains comprehensive API documentation for DaemonEye, covering all public interfaces, data structures, and usage examples.
Core API#
The core API provides the fundamental interfaces for process monitoring, alerting, and data management.
API Overview#
Component APIs#
DaemonEye provides APIs for each component:
- ProcMonD API: Process collection and system monitoring
- daemoneye-agent API: Alerting and orchestration
- daemoneye-cli API: Command-line interface and management
- daemoneye-lib API: Shared library interfaces
API Design Principles#
- Async-First: All APIs use async/await patterns
- Error Handling: Structured error types with
thiserror - Type Safety: Strong typing with Rust's type system
- Documentation: Comprehensive rustdoc comments
- Examples: Code examples for all public APIs
Quick Reference#
Process Collection#
use daemoneye_lib::collector::ProcessCollector;
async fn collect_processes() -> Result<(), Box<dyn std::error::Error>> {
let collector = ProcessCollector::new();
let processes = collector.collect_processes().await?;
Ok(())
}
Database Operations#
use daemoneye_lib::storage::Database;
async fn database_operations() -> Result<(), Box<dyn std::error::Error>> {
let db = Database::new("processes.db").await?;
let pid = 1234;
let processes = db
.query_processes("SELECT * FROM processes WHERE pid = ?", &[&pid])
.await?;
Ok(())
}
Alert Management#
use daemoneye_lib::alerting::{Alert, AlertManager, AlertSeverity};
use daemoneye_lib::models::ProcessInfo;
async fn alert_management() -> Result<(), Box<dyn std::error::Error>> {
let mut alert_manager = AlertManager::new();
alert_manager.add_sink(Box::new(SyslogSink::new("daemon")?));
// Create a sample alert
let alert = Alert::new(
"suspicious_process_detected",
"Process 'malware.exe' detected with suspicious behavior",
AlertSeverity::High,
Some(ProcessInfo {
pid: 1234,
name: "malware.exe".to_string(),
executable_path: Some("/tmp/malware.exe".to_string()),
..Default::default()
}),
);
alert_manager.send_alert(alert).await?;
Ok(())
}
Configuration Management#
use daemoneye_lib::config::Config;
async fn configuration_management() -> Result<(), Box<dyn std::error::Error>> {
let config = Config::load_from_file("config.yaml").await?;
let scan_interval = config.get::<u64>("app.scan_interval_ms")?;
Ok(())
}
Data Structures#
ProcessInfo#
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ProcessInfo {
pub pid: u32,
pub name: String,
pub executable_path: Option<String>,
pub command_line: Option<String>,
pub start_time: Option<DateTime<Utc>>,
pub cpu_usage: Option<f64>,
pub memory_usage: Option<u64>,
pub status: ProcessStatus,
pub executable_hash: Option<String>,
pub collection_time: DateTime<Utc>,
}
Alert#
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Alert {
pub id: Uuid,
pub rule_name: String,
pub severity: AlertSeverity,
pub message: String,
pub process: ProcessInfo,
pub timestamp: DateTime<Utc>,
pub metadata: HashMap<String, String>,
}
DetectionRule#
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct DetectionRule {
pub name: String,
pub description: String,
pub sql_query: String,
pub priority: u32,
pub enabled: bool,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
Error Types#
CollectionError#
#[derive(Debug, Error)]
pub enum CollectionError {
#[error("Permission denied accessing process {pid}")]
PermissionDenied { pid: u32 },
#[error("Process {pid} no longer exists")]
ProcessNotFound { pid: u32 },
#[error("I/O error: {0}")]
IoError(#[from] std::io::Error),
#[error("System error: {0}")]
SystemError(String),
}
DatabaseError#
#[derive(Debug, Error)]
pub enum DatabaseError {
#[error("Database connection failed: {0}")]
ConnectionFailed(String),
#[error("Query execution failed: {0}")]
QueryFailed(String),
#[error("Transaction failed: {0}")]
TransactionFailed(String),
#[error("SQLite error: {0}")]
SqliteError(#[from] rusqlite::Error),
}
AlertError#
#[derive(Debug, Error)]
pub enum AlertError {
#[error("Alert delivery failed: {0}")]
DeliveryFailed(String),
#[error("Invalid alert format: {0}")]
InvalidFormat(String),
#[error("Alert sink error: {0}")]
SinkError(String),
#[error("Alert queue full")]
QueueFull,
}
Service Traits#
ProcessCollectionService#
#[async_trait]
pub trait ProcessCollectionService: Send + Sync {
async fn collect_processes(&self) -> Result<CollectionResult, CollectionError>;
async fn get_system_info(&self) -> Result<SystemInfo, CollectionError>;
async fn get_process_by_pid(&self, pid: u32) -> Result<Option<ProcessInfo>, CollectionError>;
}
DetectionService#
#[async_trait]
pub trait DetectionService: Send + Sync {
async fn execute_rules(&self, scan_context: &ScanContext)
-> Result<Vec<Alert>, DetectionError>;
async fn load_rules(&self) -> Result<Vec<DetectionRule>, DetectionError>;
async fn add_rule(&self, rule: DetectionRule) -> Result<(), DetectionError>;
async fn remove_rule(&self, rule_name: &str) -> Result<(), DetectionError>;
}
AlertSink#
#[async_trait]
pub trait AlertSink: Send + Sync {
async fn send(&self, alert: &Alert) -> Result<DeliveryResult, DeliveryError>;
async fn health_check(&self) -> HealthStatus;
fn name(&self) -> &str;
}
Configuration API#
Config#
pub struct Config {
app: AppConfig,
database: DatabaseConfig,
alerting: AlertingConfig,
security: SecurityConfig,
}
impl Config {
pub async fn load_from_file(path: &str) -> Result<Self, ConfigError>;
pub async fn load_from_env() -> Result<Self, ConfigError>;
pub fn get<T>(&self, key: &str) -> Result<T, ConfigError>
where
T: DeserializeOwned;
pub fn set<T>(&mut self, key: &str, value: T) -> Result<(), ConfigError>
where
T: Serialize;
pub fn validate(&self) -> Result<(), ConfigError>;
}
AppConfig#
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct AppConfig {
pub scan_interval_ms: u64,
pub batch_size: usize,
pub log_level: String,
pub data_dir: PathBuf,
pub log_dir: PathBuf,
pub pid_file: Option<PathBuf>,
pub user: Option<String>,
pub group: Option<String>,
pub max_memory_mb: Option<u64>,
pub max_cpu_percent: Option<f64>,
}
Database API#
Database#
pub struct Database {
conn: Connection,
}
impl Database {
pub async fn new(path: &str) -> Result<Self, DatabaseError>;
pub async fn create_schema(&self) -> Result<(), DatabaseError>;
pub async fn insert_process(&self, process: &ProcessInfo) -> Result<(), DatabaseError>;
pub async fn get_process(&self, pid: u32) -> Result<Option<ProcessInfo>, DatabaseError>;
pub async fn query_processes(
&self,
query: &str,
params: &[&dyn ToSql],
) -> Result<Vec<ProcessInfo>, DatabaseError>;
pub async fn insert_alert(&self, alert: &Alert) -> Result<(), DatabaseError>;
pub async fn get_alerts(&self, limit: Option<usize>) -> Result<Vec<Alert>, DatabaseError>;
pub async fn cleanup_old_data(&self, retention_days: u32) -> Result<(), DatabaseError>;
}
Alerting API#
AlertManager#
pub struct AlertManager {
sinks: Vec<Box<dyn AlertSink>>,
queue: Arc<Mutex<VecDeque<Alert>>>,
max_queue_size: usize,
}
impl AlertManager {
pub fn new() -> Self;
pub fn add_sink(&mut self, sink: Box<dyn AlertSink>);
pub async fn send_alert(&self, alert: Alert) -> Result<(), AlertError>;
pub async fn health_check(&self) -> HealthStatus;
pub fn queue_size(&self) -> usize;
pub fn queue_capacity(&self) -> usize;
}
Alert Sinks#
SyslogSink#
pub struct SyslogSink {
facility: String,
priority: String,
tag: String,
}
impl SyslogSink {
pub fn new(facility: &str) -> Result<Self, SinkError>;
pub fn with_priority(self, priority: &str) -> Self;
pub fn with_tag(self, tag: &str) -> Self;
}
WebhookSink#
pub struct WebhookSink {
url: String,
method: String,
timeout: Duration,
retry_attempts: u32,
headers: HashMap<String, String>,
}
impl WebhookSink {
pub fn new(url: &str) -> Self;
pub fn with_method(self, method: &str) -> Self;
pub fn with_timeout(self, timeout: Duration) -> Self;
pub fn with_retry_attempts(self, attempts: u32) -> Self;
pub fn with_header(self, key: &str, value: &str) -> Self;
}
FileSink#
pub struct FileSink {
path: PathBuf,
format: OutputFormat,
rotation: RotationPolicy,
max_files: usize,
}
impl FileSink {
pub fn new(path: &str) -> Self;
pub fn with_format(self, format: OutputFormat) -> Self;
pub fn with_rotation(self, rotation: RotationPolicy) -> Self;
pub fn with_max_files(self, max_files: usize) -> Self;
}
CLI API#
Cli#
pub struct Cli {
pub command: Commands,
pub config: Option<PathBuf>,
pub log_level: String,
}
#[derive(Subcommand)]
pub enum Commands {
Run(RunCommand),
Config(ConfigCommand),
Rules(RulesCommand),
Alerts(AlertsCommand),
Health(HealthCommand),
Query(QueryCommand),
Logs(LogsCommand),
}
impl Cli {
pub async fn execute(self) -> Result<(), CliError>;
}
Commands#
RunCommand#
#[derive(Args)]
pub struct RunCommand {
#[arg(short, long)]
pub daemon: bool,
#[arg(short, long)]
pub foreground: bool,
}
ConfigCommand#
#[derive(Subcommand)]
pub enum ConfigCommand {
Show(ConfigShowCommand),
Set(ConfigSetCommand),
Get(ConfigGetCommand),
Validate(ConfigValidateCommand),
Load(ConfigLoadCommand),
}
#[derive(Args)]
pub struct ConfigShowCommand {
#[arg(long)]
pub include_defaults: bool,
#[arg(long)]
pub format: Option<String>,
}
RulesCommand#
#[derive(Subcommand)]
pub enum RulesCommand {
List(RulesListCommand),
Add(RulesAddCommand),
Remove(RulesRemoveCommand),
Enable(RulesEnableCommand),
Disable(RulesDisableCommand),
Validate(RulesValidateCommand),
Test(RulesTestCommand),
Reload(RulesReloadCommand),
}
IPC API#
IpcServer#
pub struct IpcServer {
socket_path: PathBuf,
handlers: HashMap<String, Box<dyn IpcHandler>>,
}
impl IpcServer {
pub fn new(socket_path: &str) -> Self;
pub fn add_handler(&mut self, name: &str, handler: Box<dyn IpcHandler>);
pub async fn run(self) -> Result<(), IpcError>;
}
IpcClient#
pub struct IpcClient {
socket_path: PathBuf,
connection: Option<Connection>,
}
impl IpcClient {
pub async fn new(socket_path: &str) -> Result<Self, IpcError>;
pub async fn send_request(&self, request: IpcRequest) -> Result<IpcResponse, IpcError>;
pub async fn close(self) -> Result<(), IpcError>;
}
IPC Messages#
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IpcRequest {
CollectProcesses,
GetProcess { pid: u32 },
QueryProcesses { query: String, params: Vec<Value> },
GetAlerts { limit: Option<usize> },
SendAlert { alert: Alert },
HealthCheck,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum IpcResponse {
Processes(Vec<ProcessInfo>),
Process(Option<ProcessInfo>),
QueryResult(Vec<ProcessInfo>),
Alerts(Vec<Alert>),
AlertSent,
Health(HealthStatus),
Error(String),
}
Utility APIs#
Logger#
pub struct Logger {
level: Level,
format: LogFormat,
output: LogOutput,
}
impl Logger {
pub fn new() -> Self;
pub fn with_level(self, level: Level) -> Self;
pub fn with_format(self, format: LogFormat) -> Self;
pub fn with_output(self, output: LogOutput) -> Self;
pub fn init(self) -> Result<(), LogError>;
}
Metrics#
pub struct Metrics {
registry: Registry,
}
impl Metrics {
pub fn new() -> Self;
pub fn counter(&self, name: &str) -> Counter;
pub fn gauge(&self, name: &str) -> Gauge;
pub fn histogram(&self, name: &str) -> Histogram;
pub fn register(&self, metric: Box<dyn Metric>) -> Result<(), MetricsError>;
}
Error Handling#
Error Types#
All APIs use structured error types:
#[derive(Debug, Error)]
pub enum DaemonEyeError {
#[error("Collection error: {0}")]
Collection(#[from] CollectionError),
#[error("Database error: {0}")]
Database(#[from] DatabaseError),
#[error("Alert error: {0}")]
Alert(#[from] AlertError),
#[error("Configuration error: {0}")]
Config(#[from] ConfigError),
#[error("IPC error: {0}")]
Ipc(#[from] IpcError),
#[error("CLI error: {0}")]
Cli(#[from] CliError),
}
Error Context#
Use anyhow for error context:
use anyhow::{Context, Result};
pub async fn collect_processes() -> Result<Vec<ProcessInfo>> {
let processes = sysinfo::System::new_all()
.processes()
.values()
.map(|p| ProcessInfo::from(p))
.collect::<Vec<_>>();
Ok(processes)
}
Examples#
Basic Process Monitoring#
use daemoneye_lib::alerting::AlertManager;
use daemoneye_lib::collector::ProcessCollector;
use daemoneye_lib::storage::Database;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Initialize components
let collector = ProcessCollector::new();
let db = Database::new("processes.db").await?;
let mut alert_manager = AlertManager::new();
// Add alert sink
alert_manager.add_sink(Box::new(SyslogSink::new("daemon")?));
// Collect processes
let processes = collector.collect_processes().await?;
// Store in database
for process in &processes {
db.insert_process(process).await?;
}
// Check for suspicious processes
let suspicious = db
.query_processes(
"SELECT * FROM processes WHERE name LIKE '%suspicious%'",
&[],
)
.await?;
// Send alerts
for process in suspicious {
let alert = Alert::new("suspicious_process", process);
alert_manager.send_alert(alert).await?;
}
Ok(())
}
Custom Alert Sink#
use async_trait::async_trait;
use daemoneye_lib::alerting::{Alert, AlertSink, DeliveryError, DeliveryResult};
pub struct CustomSink {
endpoint: String,
client: reqwest::Client,
}
impl CustomSink {
pub fn new(endpoint: &str) -> Self {
Self {
endpoint: endpoint.to_string(),
client: reqwest::Client::new(),
}
}
}
#[async_trait]
impl AlertSink for CustomSink {
async fn send(&self, alert: &Alert) -> Result<DeliveryResult, DeliveryError> {
let response = self
.client
.post(&self.endpoint)
.json(alert)
.send()
.await
.map_err(|e| DeliveryError::Network(e.to_string()))?;
if response.status().is_success() {
Ok(DeliveryResult::Success)
} else {
Err(DeliveryError::Http(response.status().as_u16()))
}
}
async fn health_check(&self) -> HealthStatus {
match self.client.get(&self.endpoint).send().await {
Ok(response) if response.status().is_success() => HealthStatus::Healthy,
_ => HealthStatus::Unhealthy,
}
}
fn name(&self) -> &str {
"custom_sink"
}
}
This API reference provides comprehensive documentation for all DaemonEye APIs. For additional examples and usage patterns, consult the specific API documentation.