libddwaf/lib.rs
1#![deny(
2 clippy::correctness,
3 clippy::pedantic,
4 clippy::perf,
5 clippy::style,
6 clippy::suspicious
7)]
8#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
9
10//! Rust bindings for the [`libddwaf` library](https://github.com/DataDog/libddwaf).
11//!
12//! # Basic Use
13//!
14//! The following high-level steps are typically used:
15//! 1. Create a new [Builder]
16//! 2. Add new configurations to it using [`Builder::add_or_update_config`]
17//! * Rulesets are often parsed from JSON documents using `serde_json`, via
18//! the `serde` feature.
19//! 3. Call [`Builder::build`] to obtain a new [`Handle`]
20//! 4. For any service request:
21//! 1. Call [`Handle::new_context`] to obtain a new [`Context`]
22//! 2. Call [`Context::run`] as appropriate with the necessary address data
23//!
24//! ```rust
25//! use libddwaf::{
26//! object::*,
27//! waf_array,
28//! waf_map,
29//! Builder,
30//! Config,
31//! RunnableContext,
32//! RunResult,
33//! };
34//!
35//! let mut builder = Builder::new(Some(&Config::default()))
36//! .expect("Failed to build WAF instance");
37//! let rule_set = waf_map!{
38//! /* Typically obtained by parsing a rules file using the serde feature */
39//! ("rules", waf_array!{ waf_map!{
40//! ("id", "1"),
41//! ("name", "rule 1"),
42//! ("tags", waf_map!{ ("type", "flow1"), ("category", "test") }),
43//! ("conditions", waf_array!{ waf_map!{
44//! ("operator", "match_regex"),
45//! ("parameters", waf_map!{
46//! ("regex", ".*"),
47//! ("inputs", waf_array!{ waf_map!{ ("address", "arg1" )} }),
48//! }),
49//! } }),
50//! ("on_match", waf_array!{ "block" })
51//! } }),
52//! };
53//! let mut diagnostics = WafOwnedDefaultAllocator::<WafMap>::default();
54//! if !builder.add_or_update_config("config/file/logical/path", &rule_set, Some(&mut diagnostics)) {
55//! panic!("Failed to add or update config!");
56//! }
57//! let waf = builder.build().expect("Failed to build WAF instance");
58//!
59//! // For each new request to be monitored...
60//! let mut waf_ctx = waf.new_context();
61//! let data = waf_map!{
62//! ("arg1", "value1"),
63//! };
64//! match waf_ctx.run(data, std::time::Duration::from_millis(1)) {
65//! // Deal with the result as appropriate...
66//! Ok(RunResult::Match(res)) => {
67//! assert!(!res.timeout());
68//! assert!(res.keep());
69//! assert!(res.duration() >= std::time::Duration::default());
70//! assert_eq!(res.events().expect("Expected events").len(), 1);
71//! assert_eq!(res.actions().expect("Expected actions").len(), 1);
72//! assert_eq!(res.attributes().expect("Expected attributes").len(), 0);
73//! },
74//! Err(e) => panic!("Error while running the in-app WAF: {e}"),
75//! _ => panic!("Unexpected result"),
76//! }
77//! ```
78
79use std::ffi::CStr;
80
81#[cfg(feature = "serde")]
82pub mod serde;
83
84pub mod log;
85pub mod object;
86mod private;
87
88macro_rules! forward {
89 ($($name:ident),*) => {
90 $(
91 mod $name;
92 #[doc(inline)]
93 pub use $name::*;
94 )*
95 };
96}
97
98forward!(builder, config, context, handle);
99
100/// Returns the version of the underlying `libddwaf` library.
101#[must_use]
102pub fn version() -> &'static CStr {
103 let ptr = unsafe { libddwaf_sys::ddwaf_get_version() };
104 if ptr.is_null() {
105 unsafe { CStr::from_ptr("\0".as_ptr().cast()) }
106 } else {
107 unsafe { CStr::from_ptr(ptr) }
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 #[test]
114 #[cfg(not(miri))]
115 fn test_version() {
116 use crate::version;
117
118 if std::env::var("LIBDDWAF_PREFIX").is_ok() {
119 eprintln!("Skipping test_get_version: LIBDDWAF_PREFIX is set");
120 return;
121 }
122
123 assert_eq!(
124 env!("CARGO_PKG_VERSION"),
125 version()
126 .to_str()
127 .expect("Failed to convert version to str")
128 );
129 }
130}