Skip to content

withzombies/osquery-rust

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

53 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Crate Docs Status Apache 2.0 Licensed MIT Licensed

osquery-rust-ng

By providing Rust bindings for Osquery this crate facilitates the implementation of Osquery extensions.

Features

  • âś… Table plugins - Create custom tables to query system information
  • âś… Logger plugins - Implement custom logging backends for osquery
  • âś… Config plugins - Provide custom configuration sources for osquery
  • âś… Writable tables - Support for INSERT, UPDATE, and DELETE operations
  • 🦀 Pure Rust - No C/C++ dependencies, just safe Rust code
  • 🚀 High performance - Minimal overhead for extensions
  • 📦 Easy to use - Simple API with examples to get started quickly

Building

Clone the repository and build the workspace:

git clone https://github.com/withzombies/osquery-rust.git
cd osquery-rust
cargo build --workspace

Run tests:

cargo test --workspace

The project uses a workspace structure with the main library and several examples. All examples are built automatically when you build the workspace.

Quick Start

Here's a simple example of creating a read-only table plugin:

use osquery_rust_ng::prelude::*;
use osquery_rust_ng::plugin::{ColumnDef, ColumnType, ColumnOptions, Plugin, ReadOnlyTable};
use std::collections::BTreeMap;

struct MyTable;

impl ReadOnlyTable for MyTable {
    fn name(&self) -> String {
        "my_table".to_string()
    }

    fn columns(&self) -> Vec<ColumnDef> {
        vec![
            ColumnDef::new("greeting", ColumnType::Text, ColumnOptions::DEFAULT),
            ColumnDef::new("count", ColumnType::Integer, ColumnOptions::DEFAULT),
        ]
    }

    fn generate(&self, _req: ExtensionPluginRequest) -> ExtensionResponse {
        let row = BTreeMap::from([
            ("greeting".to_string(), "Hello, osquery!".to_string()),
            ("count".to_string(), "42".to_string()),
        ]);
        ExtensionResponse::new(ExtensionStatus::default(), vec![row])
    }

    fn shutdown(&self) {
        // Called when the extension is shutting down
    }
}

fn main() -> std::io::Result<()> {
    let mut server = Server::new(None, "/path/to/osquery/socket")?;
    server.register_plugin(Plugin::readonly_table(MyTable));
    server.run().map_err(std::io::Error::other)
}

Migrating to 2.0

Version 2.0 simplifies the shutdown API by removing ShutdownReason. If upgrading from 1.x:

Before (1.x):

fn shutdown(&self, reason: ShutdownReason) {
    println!("Shutting down: {reason}");
}

After (2.0):

fn shutdown(&self) {
    println!("Shutting down");
}

This affects all plugin traits: ReadOnlyTable, Table, LoggerPlugin, and ConfigPlugin.

The Server::stop() and ServerStopHandle::stop() methods also no longer take a reason parameter.

Usage Guide

Creating Table Plugins

Table plugins allow you to expose data as SQL tables in osquery. There are two types:

  1. Read-only tables - Implement the ReadOnlyTable trait
  2. Writable tables - Implement the Table trait for full CRUD operations

Creating Logger Plugins

Logger plugins receive log data from osquery and can forward it to various backends:

use osquery_rust_ng::plugin::{LoggerPlugin, LogStatus, LoggerFeatures};

struct MyLogger;

impl LoggerPlugin for MyLogger {
    fn name(&self) -> String {
        "my_logger".to_string()
    }

    fn log_string(&self, message: &str) -> Result<(), String> {
        println!("Log: {}", message);
        Ok(())
    }

    fn log_status(&self, status: &LogStatus) -> Result<(), String> {
        println!("[{}] {}:{} - {}",
            status.severity, status.filename, status.line, status.message);
        Ok(())
    }

    // Advertise support for status logs (enabled by default)
    fn features(&self) -> i32 {
        LoggerFeatures::LOG_STATUS
    }
}

The features() method tells osquery what log types your plugin supports. By default, loggers receive status logs (INFO/WARNING/ERROR from osquery internals). Available features:

  • LoggerFeatures::BLANK - Query results only
  • LoggerFeatures::LOG_STATUS - Status logs (default)
  • LoggerFeatures::LOG_EVENT - Event logs

Creating Config Plugins

Config plugins provide configuration data to osquery, allowing dynamic configuration management:

use osquery_rust_ng::plugin::ConfigPlugin;
use std::collections::HashMap;

struct MyConfig;

impl ConfigPlugin for MyConfig {
    fn name(&self) -> String {
        "my_config".to_string()
    }

    fn gen_config(&self) -> Result<HashMap<String, String>, String> {
        let mut config_map = HashMap::new();
        
        // Provide JSON configuration
        let config = r#"{
            "options": {
                "host_identifier": "hostname",
                "schedule_splay_percent": 10
            },
            "schedule": {
                "heartbeat": {
                    "query": "SELECT version FROM osquery_info;",
                    "interval": 3600
                }
            }
        }"#;
        
        config_map.insert("main".to_string(), config.to_string());
        Ok(config_map)
    }

    fn gen_pack(&self, name: &str, _value: &str) -> Result<String, String> {
        // Optionally provide query packs
        Err(format!("Pack '{}' not found", name))
    }
}

Integration with osquery

There are three ways to run your extension:

  1. Direct loading: osqueryi --extension /path/to/extension
  2. Socket connection: Run extension separately with --socket /path/to/osquery.sock
  3. Auto-loading: Place extension in osquery's autoload directory

See the examples directory for complete implementations.

Graceful Shutdown

Extensions support graceful shutdown through multiple mechanisms:

Signal Handling (recommended for production)

Use run_with_signal_handling() to automatically handle SIGTERM and SIGINT:

fn main() -> std::io::Result<()> {
    let mut server = Server::new(None, "/path/to/socket")?;
    server.register_plugin(Plugin::readonly_table(MyTable));

    // Handles SIGTERM (systemd) and SIGINT (Ctrl+C)
    server.run_with_signal_handling().map_err(std::io::Error::other)
}

Programmatic Shutdown

Use ServerStopHandle to stop the server from another thread:

let mut server = Server::new(None, "/path/to/socket")?;
let handle = server.get_stop_handle();

// In another thread or signal handler:
std::thread::spawn(move || {
    // ... wait for condition ...
    handle.stop();
});

server.run()?;

Shutdown Lifecycle

When shutdown is triggered (via signal, osquery RPC, or stop()):

  1. The server deregisters from osquery
  2. All plugins receive a shutdown() callback
  3. The socket file is cleaned up
  4. run() returns

Examples

The repository includes several complete examples:

Each example includes its own README with specific build and usage instructions.

Contributing

We welcome contributions! Here's how to get started:

Development Setup

  1. Fork and clone the repository
  2. Install the pre-commit hook:
    cp .hooks/pre-commit .git/hooks/pre-commit
    chmod +x .git/hooks/pre-commit

Code Quality Standards

This project maintains high code quality standards:

  • All code must pass cargo fmt
  • No clippy warnings allowed (enforced by CI)
  • All tests must pass
  • Unsafe code must be documented

The pre-commit hook automatically runs these checks.

Testing

Run the full test suite:

cargo test --workspace

Pull Request Process

  1. Create a feature branch from main
  2. Write tests for new functionality
  3. Ensure all checks pass
  4. Submit a PR with a clear description
  5. Address review feedback

Reporting Issues

Please report issues on GitHub with:

  • osquery version
  • Rust version
  • Operating system
  • Steps to reproduce
  • Expected vs actual behavior

Project Structure

The project is organized as a Cargo workspace:

  • osquery-rust/ - The main library crate with Thrift bindings and plugin framework
  • examples/ - Working examples demonstrating different plugin types:
    • table-proc-meminfo/ - Read-only table example
    • writeable-table/ - Full CRUD table example
    • two-tables/ - Multiple tables in one extension
    • logger-file/ - File logger plugin
    • logger-syslog/ - Syslog logger plugin
    • config-file/ - An example that loads a config from a json file
    • config-static/ - An example that provides a static config

Additional Resources

Related Projects

This project contributed the support for Unix Domain Sockets to Apache Thrift's Rust crate.

This project was initially forked from polarlab's osquery-rust project.

Links

About

Rust bindings for osquery

Topics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Languages