Skip to main content

opseclint_core/
kb.rs

1//! Knowledge-base loading and matching. Each platform's KB is embedded at
2//! compile time so the tool ships as a single self-contained binary.
3
4use crate::model::KnowledgeBase;
5
6const EMBEDDED_LINUX: &str = include_str!("../data/knowledge.json");
7const EMBEDDED_WINDOWS: &str = include_str!("../data/knowledge-windows.json");
8const EMBEDDED_MACOS: &str = include_str!("../data/knowledge-macos.json");
9
10/// The host platform / telemetry model an analysis targets.
11#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
13pub enum Platform {
14    /// Linux hosts with auditd / EDR syscall telemetry.
15    #[cfg_attr(feature = "clap", value(name = "linux-auditd", alias = "linux"))]
16    LinuxAuditd,
17    /// Windows hosts with Sysmon / Security-log telemetry.
18    #[cfg_attr(feature = "clap", value(name = "windows-sysmon", alias = "windows"))]
19    WindowsSysmon,
20    /// macOS hosts with Endpoint Security (ESF) / unified-log telemetry.
21    #[cfg_attr(feature = "clap", value(name = "macos-es", alias = "macos"))]
22    MacosEs,
23}
24
25impl Platform {
26    /// The Sigma `logsource.product` value to filter rules by for this platform.
27    pub fn sigma_product(self) -> &'static str {
28        match self {
29            Platform::LinuxAuditd => "linux",
30            Platform::WindowsSysmon => "windows",
31            Platform::MacosEs => "macos",
32        }
33    }
34}
35
36/// Why a knowledge base failed to load.
37///
38/// The two cases are different kinds of wrong and a caller may want to treat
39/// them differently: [`Parse`](KbError::Parse) means the JSON does not describe
40/// a knowledge base at all, while [`Invalid`](KbError::Invalid) means it
41/// deserialized fine but breaks an invariant the analysis relies on. Neither is
42/// reachable from [`load`] with the embedded bases — those are validated by the
43/// test suite — but both are reachable once you deserialize a
44/// [`KnowledgeBase`] of your own and call [`KnowledgeBase::validate`].
45#[derive(Debug)]
46pub enum KbError {
47    /// The JSON could not be deserialized into a [`KnowledgeBase`].
48    Parse(serde_json::Error),
49    /// The knowledge base deserialized but violates a cross-field invariant.
50    /// Carries the offending entry's id and what it broke.
51    Invalid(String),
52}
53
54impl std::fmt::Display for KbError {
55    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
56        match self {
57            KbError::Parse(e) => write!(f, "malformed knowledge base: {e}"),
58            KbError::Invalid(m) => write!(f, "invalid knowledge base: {m}"),
59        }
60    }
61}
62
63impl std::error::Error for KbError {
64    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
65        match self {
66            KbError::Parse(e) => Some(e),
67            KbError::Invalid(_) => None,
68        }
69    }
70}
71
72impl From<serde_json::Error> for KbError {
73    fn from(e: serde_json::Error) -> Self {
74        KbError::Parse(e)
75    }
76}
77
78/// Load the embedded knowledge base for a platform.
79///
80/// Deserialization is followed by a semantic validation pass (see
81/// [`KnowledgeBase::validate`]). No I/O: every platform's base is embedded at
82/// compile time, so this is a parse of a `&'static str` and cannot fail for any
83/// reason outside this crate.
84pub fn load(platform: Platform) -> Result<KnowledgeBase, KbError> {
85    let raw = match platform {
86        Platform::LinuxAuditd => EMBEDDED_LINUX,
87        Platform::WindowsSysmon => EMBEDDED_WINDOWS,
88        Platform::MacosEs => EMBEDDED_MACOS,
89    };
90    let kb: KnowledgeBase = serde_json::from_str(raw)?;
91    kb.validate().map_err(KbError::Invalid)?;
92    Ok(kb)
93}