falco_plugin/plugin/extract/mod.rs
1use crate::extract::EventInput;
2use crate::plugin::base::Plugin;
3use crate::plugin::extract::schema::ExtractFieldInfo;
4use crate::plugin::extract::wrappers::ExtractPluginExported;
5use crate::tables::LazyTableReader;
6use falco_event::events::types::EventType;
7use falco_plugin_api::ss_plugin_extract_field;
8use std::any::TypeId;
9use std::collections::BTreeMap;
10use std::ffi::{CStr, CString};
11use std::sync::Mutex;
12
13mod extractor_fn;
14pub mod fields;
15pub mod schema;
16#[doc(hidden)]
17pub mod wrappers;
18
19/// The actual argument passed to the extractor function
20///
21/// It is validated based on the [`ExtractFieldInfo`] definition (use [`ExtractFieldInfo::with_arg`]
22/// to specify the expected argument type).
23///
24/// **Note**: this type describes the actual argument in a particular invocation.
25/// For describing the type of arguments the extractor accepts, please see [`ExtractArgType`]`
26#[derive(Debug, Clone, Eq, PartialEq)]
27pub enum ExtractFieldRequestArg<'a> {
28 /// no argument, the extractor was invoked as plain `field_name`
29 None,
30 /// an integer argument, the extractor was invoked as e.g. `field_name[1]`
31 Int(u64),
32 /// a string argument, the extractor was invoked as e.g. `field_name[foo]`
33 String(&'a CStr),
34}
35
36pub trait ExtractField {
37 unsafe fn key_unchecked(&self) -> ExtractFieldRequestArg;
38}
39
40impl ExtractField for ss_plugin_extract_field {
41 unsafe fn key_unchecked(&self) -> ExtractFieldRequestArg {
42 if self.arg_present == 0 {
43 return ExtractFieldRequestArg::None;
44 }
45
46 if self.arg_key.is_null() {
47 return ExtractFieldRequestArg::Int(self.arg_index);
48 }
49
50 unsafe { ExtractFieldRequestArg::String(CStr::from_ptr(self.arg_key)) }
51 }
52}
53
54/// An extraction request
55#[derive(Debug)]
56pub struct ExtractRequest<'c, 'e, 't, P: ExtractPlugin> {
57 /// a context instance, potentially shared between extractions
58 pub context: &'c mut P::ExtractContext,
59
60 /// the event being processed
61 pub event: &'e EventInput,
62
63 /// an interface to access tables exposed from Falco core and other plugins
64 ///
65 /// See [`crate::tables`] for details
66 pub table_reader: &'t LazyTableReader<'t>,
67}
68
69/// # Support for field extraction plugins
70pub trait ExtractPlugin: Plugin + ExtractPluginExported + Sized
71where
72 Self: 'static,
73{
74 /// The set of event types supported by this plugin
75 ///
76 /// If empty, the plugin will get invoked for all event types, otherwise it will only
77 /// get invoked for event types from this list.
78 ///
79 /// **Note**: some notable event types are:
80 /// - [`EventType::ASYNCEVENT_E`], generated from async plugins
81 /// - [`EventType::PLUGINEVENT_E`], generated from source plugins
82 const EVENT_TYPES: &'static [EventType];
83 /// The set of event sources supported by this plugin
84 ///
85 /// If empty, the plugin will get invoked for events coming from all sources, otherwise it will
86 /// only get invoked for events from sources named in this list.
87 ///
88 /// **Note**: one notable event source is called `syscall`
89 const EVENT_SOURCES: &'static [&'static str];
90
91 /// The extraction context
92 ///
93 /// It might be useful if your plugin supports multiple fields, and they all share some common
94 /// preprocessing steps. Instead of redoing the preprocessing for each field, intermediate
95 /// results can be stored in the context for subsequent extractions (from the same event).
96 ///
97 /// If you do not need a context to share between extracting fields of the same event, use `()`
98 /// as the type.
99 ///
100 /// Since the context is created using the [`Default`] trait, you may prefer to use an Option
101 /// wrapping the actual context type:
102 ///
103 /// ```ignore
104 /// impl ExtractPlugin for MyPlugin {
105 /// type ExtractContext = Option<ActualContext>;
106 /// // ...
107 /// }
108 ///
109 /// impl MyPlugin {
110 /// fn make_context(&mut self, ...) -> ActualContext { /* ... */ }
111 ///
112 /// fn extract_field_one(
113 /// &mut self,
114 /// req: ExtractContext<Self>) -> ... {
115 /// let context = req.context.get_or_insert_with(|| self.make_context(...));
116 ///
117 /// // use context
118 /// }
119 /// }
120 /// ```
121 type ExtractContext: Default + 'static;
122
123 /// The actual list of extractable fields
124 ///
125 /// An extraction method is a method with the following signature:
126 /// ```ignore
127 /// use anyhow::Error;
128 /// use falco_plugin::extract::{EventInput, ExtractFieldRequestArg, ExtractRequest};
129 /// use falco_plugin::tables::TableReader;
130 ///
131 /// fn extract_sample(
132 /// &mut self,
133 /// req: ExtractRequest<Self>,
134 /// arg: A, // optional
135 /// ) -> Result<R, Error>;
136 ///
137 /// ```
138 /// where `R` is one of the following types or a [`Vec`] of them:
139 /// - [`u64`]
140 /// - [`bool`]
141 /// - [`CString`]
142 /// - [`std::time::SystemTime`]
143 /// - [`std::time::Duration`]
144 /// - [`std::net::IpAddr`]
145 /// - [`falco_event::fields::types::PT_IPNET`]
146 ///
147 /// and `A` is the argument to the field extraction:
148 ///
149 /// | Argument declaration | `field` lookup | `field[5]` lookup | `field[foo]` lookup |
150 /// |----------------------|----------------|-------------------|---------------------|
151 /// | _missing_ | valid | - | - |
152 /// | `arg: u64` | - | valid | - |
153 /// | `arg: Option<u64>` | valid | valid | - |
154 /// | `arg: &CStr` | - | - | valid |
155 /// | `arg: Option<&CStr>` | valid | - | valid |
156 ///
157 /// `req` is the extraction request ([`ExtractRequest`]), containing the context in which
158 /// the plugin is doing the work.
159 ///
160 /// To register extracted fields, add them to the [`ExtractPlugin::EXTRACT_FIELDS`] array, wrapped via [`crate::extract::field`]:
161 /// ```
162 /// use std::ffi::CStr;
163 /// use falco_plugin::event::events::types::EventType;
164 /// use falco_plugin::event::events::types::EventType::PLUGINEVENT_E;
165 /// use falco_plugin::anyhow::Error;
166 /// use falco_plugin::base::Plugin;
167 /// use falco_plugin::extract::{
168 /// field,
169 /// ExtractFieldInfo,
170 /// ExtractPlugin,
171 /// ExtractRequest};
172 ///# use falco_plugin::{extract_plugin, plugin};
173 /// use falco_plugin::tables::TablesInput;
174 ///
175 /// struct SampleExtractPlugin;
176 ///
177 /// impl Plugin for SampleExtractPlugin {
178 /// const NAME: &'static CStr = c"dummy";
179 /// const PLUGIN_VERSION: &'static CStr = c"0.0.0";
180 /// const DESCRIPTION: &'static CStr = c"test plugin";
181 /// const CONTACT: &'static CStr = c"rust@localdomain.pl";
182 /// type ConfigType = ();
183 ///
184 /// fn new(_input: Option<&TablesInput>, _config: Self::ConfigType) -> Result<Self, Error> {
185 /// Ok(Self)
186 /// }
187 /// }
188 ///
189 /// impl SampleExtractPlugin {
190 /// fn extract_sample(
191 /// &mut self,
192 /// _req: ExtractRequest<Self>,
193 /// ) -> Result<u64, Error> {
194 /// Ok(10u64)
195 /// }
196 ///
197 /// fn extract_arg(
198 /// &mut self,
199 /// _req: ExtractRequest<Self>,
200 /// arg: u64,
201 /// ) -> Result<u64, Error> {
202 /// Ok(arg)
203 /// }
204 /// }
205 ///
206 /// impl ExtractPlugin for SampleExtractPlugin {
207 /// const EVENT_TYPES: &'static [EventType] = &[PLUGINEVENT_E];
208 /// const EVENT_SOURCES: &'static [&'static str] = &["dummy"];
209 /// type ExtractContext = ();
210 /// const EXTRACT_FIELDS: &'static [ExtractFieldInfo<Self>] = &[
211 /// field("sample.always_10", &Self::extract_sample),
212 /// field("sample.arg", &Self::extract_arg)
213 /// ];
214 /// }
215 ///
216 ///# plugin!(SampleExtractPlugin);
217 ///# extract_plugin!(SampleExtractPlugin);
218 /// ```
219 const EXTRACT_FIELDS: &'static [ExtractFieldInfo<Self>];
220
221 /// Generate the field schema for the Falco plugin framework
222 ///
223 /// The default implementation inspects all fields from [`Self::EXTRACT_FIELDS`] and generates
224 /// a JSON description in the format expected by the framework.
225 ///
226 /// You probably won't need to provide your own implementation.
227 fn get_fields() -> &'static CStr {
228 static FIELD_SCHEMA: Mutex<BTreeMap<TypeId, CString>> = Mutex::new(BTreeMap::new());
229
230 let ty = TypeId::of::<Self>();
231 let mut schema_map = FIELD_SCHEMA.lock().unwrap();
232 // Safety:
233 //
234 // we only generate the string once and never change or delete it
235 // so the pointer should remain valid for the static lifetime
236 // hence the dance of converting a reference to a raw pointer and back
237 // to erase the lifetime
238 unsafe {
239 CStr::from_ptr(
240 schema_map
241 .entry(ty)
242 .or_insert_with(|| {
243 let schema = serde_json::to_string_pretty(&Self::EXTRACT_FIELDS)
244 .expect("failed to serialize extraction schema");
245 CString::new(schema.into_bytes())
246 .expect("failed to add NUL to extraction schema")
247 })
248 .as_ptr(),
249 )
250 }
251 }
252
253 /// Perform the actual field extraction
254 ///
255 /// The default implementation creates an empty context and loops over all extraction
256 /// requests, invoking the relevant function to actually generate the field value.
257 ///
258 /// You probably won't need to provide your own implementation.
259 fn extract_fields<'a>(
260 &'a mut self,
261 event_input: &EventInput,
262 table_reader: &LazyTableReader,
263 fields: &mut [ss_plugin_extract_field],
264 storage: &'a mut bumpalo::Bump,
265 ) -> Result<(), anyhow::Error> {
266 let mut context = Self::ExtractContext::default();
267
268 for req in fields {
269 let info = Self::EXTRACT_FIELDS
270 .get(req.field_id as usize)
271 .ok_or_else(|| anyhow::anyhow!("field index out of bounds"))?;
272
273 let request = ExtractRequest::<Self> {
274 context: &mut context,
275 event: event_input,
276 table_reader,
277 };
278
279 info.func.call(self, req, request, storage)?;
280 }
281 Ok(())
282 }
283}