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}