tau_engine/lib.rs
1//! # Tau Engine
2//!
3//! This crate provides a library that tags documents by running and matching rules over them.
4//! The engine makes use of a Pratt parser and a tree solver in order to evaluate the detection
5//! logic of a rule against a document, if the outcome is true the document is considered tagged by
6//! that rule.
7//!
8//!
9//! ## Rules
10//!
11//! A rule is used to tag a document and is made up of three parts:
12//! - `detection`: the logic used to evaluate a document.
13//! - `true positives`: example documents that must evaluate to true for the given detection.
14//! - `true negatives`: example documents that must evaluate to false for the given detection.
15//!
16//! The detection block is made up of a condition, and identifiers. This allows for simple but
17//! expressive rules, below is a brief summary (see [Rules](Rule) for more):
18//!
19//! ### Identifiers
20//!
21//! Identifiers are used to help keep the condition concise and generally contain the core of the
22//! matching logic. They consist of Key/Value pairs which allow for the extraction of data from the
23//! document and the evaluate of its value. It should be noted that mappings are treated as
24//! conjunctions, while sequences are treated as disjunctions.
25//!
26//! Identifiers make use of the following matching logic:
27//! - `foobar`: an exact match of foobar
28//! - `foobar*`: starts with foobar
29//! - `*foobar`: ends with foobar
30//! - `*foobar*`: contains foobar
31//! - `?foobar`: regex foobar
32//!
33//! Any of the above can be made case insensitive with the `i` prefix, for example:
34//! - `ifoobar`
35//! - `ifoobar*`
36//!
37//! Escaping can be achieved with a combination of `'` and `"`.
38//!
39//! ### Condition
40//!
41//! The condition is just a boolean expression and supports the following:
42//! - `and`: logical conjunction
43//! - `or`: logical disjunction
44//! - `==`: equality comparison
45//! - `>`, `>=`, `<`, `<=`: numeric comparisons
46//! - `not`: negate
47//! - `all(i)`: make sequences behave as conjunctions
48//! - `of(i, x)`: ensure a sequence has a minimum number of matches
49//!
50//!
51//! ### Examples
52//!
53//! ```text
54//! detection:
55//! A:
56//! foo.bar: foobar
57//!
58//! condition: A
59//!
60//! true_positives:
61//! - foo:
62//! bar: foobar
63//!
64//! true_negatives:
65//! - foo:
66//! bar: foo
67//! ```
68//!
69//! ## Documents
70//!
71//! A document is anything that can provide data to the engine in a meaningful way, usually through Key/Value
72//! pairs, i.e: an event log, json object, yaml file, etc. Implementations are achieved with the
73//! [`Document`](Document) trait.
74//!
75//! ## Solving
76//!
77//! This is an example of how you can tag a document against a provided rule:
78//!
79//! ```
80//! use std::borrow::Cow;
81//!
82//! use tau_engine::{Document, Rule, Value};
83//!
84//! // Define a document.
85//! struct Foo {
86//! foo: String,
87//! }
88//! impl Document for Foo {
89//! fn find(&self, key: &str) -> Option<Value<'_>> {
90//! match key {
91//! "foo" => Some(Value::String(Cow::Borrowed(&self.foo))),
92//! _ => None,
93//! }
94//! }
95//! }
96//!
97//! // Write a rule.
98//! let rule = r#"
99//! detection:
100//! A:
101//! foo: foobar
102//! condition: A
103//! true_positives:
104//! - foo: foobar
105//! true_negatives:
106//! - foo: foo
107//! "#;
108//!
109//! // Load and validate a rule.
110//! let rule = Rule::from_str(rule).unwrap();
111//! assert_eq!(rule.validate().unwrap(), true);
112//!
113//! // Create a document.
114//! let foo = Foo {
115//! foo: "foobar".to_owned(),
116//! };
117//!
118//! // Evalute the document with the rule.
119//! assert_eq!(rule.matches(&foo), true);
120//! ```
121//!
122//! ## Features
123//!
124//! The following are a list of features that can be enabled or disabled:
125//! - **core**: Exposes some of Tau Engine's internals.
126//! - **ignore_case**: Force the engine to always be case insensitive, this will ignore
127//! the `i` prefix and for that reason is not compatible with case sensitive rules.
128//! - **json**: Enable serde json support, this will allow the tau-engine to solve on
129//! `serde_json::Value`.
130//!
131//!
132//! ### JSON
133//!
134//! When JSON support is enabled for the tau-engine, the result is a solver that can now reason over
135//! any document that can be deserialized into `serde_json::Value`.
136//!
137//! ```ignore
138//! # use serde_json::json;
139//! use tau_engine::{Document, Rule};
140//!
141//! // Write a rule.
142//! let rule = r#"
143//! detection:
144//! A:
145//! foo: foobar
146//! condition: A
147//! true_positives:
148//! - foo: foobar
149//! true_negatives:
150//! - foo: foo
151//! "#;
152//!
153//! // Load and validate a rule.
154//! let rule = Rule::from_str(rule).unwrap();
155//! assert_eq!(rule.validate().unwrap(), true);
156//!
157//! // Create a document.
158//! let foo = json!({
159//! "foo": "foobar",
160//! });
161//!
162//! // Evalute the document with the rule.
163//! assert_eq!(rule.matches(&foo), true);
164//! ```
165
166#![cfg_attr(feature = "benchmarks", feature(test))]
167
168#[cfg_attr(test, macro_use)]
169#[cfg(feature = "benchmarks")]
170extern crate test;
171
172pub use self::document::Document;
173pub use self::error::{Error, Kind as ErrorKind};
174pub use self::optimiser::Optimisations;
175pub use self::rule::Rule;
176pub use self::solver::solve;
177pub use self::value::{Array, AsValue, Object, Value};
178
179pub(crate) use error::Result;
180
181mod document;
182mod error;
183mod identifier;
184#[cfg(feature = "json")]
185mod json;
186mod optimiser;
187mod parser;
188mod rule;
189mod solver;
190mod tokeniser;
191mod value;
192mod yaml;
193
194#[cfg(feature = "core")]
195/// Exposes some of Tau Engine's internals.
196pub mod core {
197 /// Exposes some of Tau Engine's internal optimisations so that Expressions can be built by hand.
198 pub mod optimiser {
199 pub use crate::optimiser::*;
200 }
201 /// Exposes some of Tau Engine's internal parsing so that Expressions can be built by hand.
202 pub mod parser {
203 pub use crate::identifier::*;
204 pub use crate::parser::*;
205 pub use crate::tokeniser::*;
206 }
207 pub use crate::rule::Detection;
208
209 use std::collections::HashMap;
210
211 use crate::document::Document;
212 use crate::parser::Expression;
213 use crate::solver::SolverResult;
214
215 lazy_static::lazy_static! {
216 static ref IDENTIFIERS: HashMap<String, Expression> = HashMap::new();
217 }
218
219 /// Evaluates a `Document` with the provided expression.
220 ///
221 /// # Panics
222 ///
223 /// This method will panic if an invalid expression is provided
224 pub fn solve(expression: &Expression, document: &dyn Document) -> bool {
225 match super::solver::solve_expression(expression, &IDENTIFIERS, document) {
226 SolverResult::True => true,
227 SolverResult::False | SolverResult::Missing => false,
228 }
229 }
230
231 /// Evaluates a `Document` with the provided expression, and identifiers.
232 ///
233 /// # Panics
234 ///
235 /// This method will panic if an invalid expression is provided
236 pub fn solve_expression(
237 expression: &Expression,
238 identifiers: &HashMap<String, Expression>,
239 document: &dyn Document,
240 ) -> bool {
241 match super::solver::solve_expression(expression, identifiers, document) {
242 SolverResult::True => true,
243 SolverResult::False | SolverResult::Missing => false,
244 }
245 }
246}