diff --git a/Cargo.lock b/Cargo.lock index b7f3dfa4..2c2acedd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -546,7 +546,7 @@ dependencies = [ "bitflags 2.9.4", "cexpr", "clang-sys", - "itertools 0.12.1", + "itertools 0.13.0", "log", "prettyplease", "proc-macro2", @@ -2637,9 +2637,9 @@ checksum = "2c4a545a15244c7d945065b5d392b2d2d7f21526fba56ce51467b06ed445e8f7" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.176" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "58f929b4d672ea937a23a1ab494143d968337a5f47e56d0815df1e0890ddf174" [[package]] name = "libeventreg" @@ -2832,6 +2832,7 @@ dependencies = [ "jsonpath-rust", "jsonpath_lib", "lazy_static", + "libc", "log", "nix 0.29.0", "once_cell", @@ -4077,7 +4078,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck", - "itertools 0.12.1", + "itertools 0.14.0", "log", "multimap", "once_cell", @@ -4097,7 +4098,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.14.0", "proc-macro2", "quote", "syn 2.0.106", @@ -4147,6 +4148,10 @@ dependencies = [ "libc", ] +[[package]] +name = "quotafs" +version = "0.1.0" + [[package]] name = "quote" version = "1.0.40" diff --git a/docs/index.rst b/docs/index.rst index 48db2230..258dd73d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -2,8 +2,28 @@ Welcome to the Project Sysinspect! ================================== .. note:: - This documentation covers **Sysinspect** — the solution to - examine any system, based on its architecture Model Description. + This documentation covers **Sysinspect** — the multi-solution to many things. + +Welcome to **Sysinspect**: originally conceived as an engine for anomaly detection and root cause analysis. The name +itself is a portmanteau of "system" and "inspection", which should give you a fair idea of its intent. + +**Sysinspect** began life as a generic engine designed to monitor system consistency, using a formal Model Description +of the system's architecture. Over time, it has grown into a collection of tools and libraries for system introspection, +configuration management, anomaly detection, root cause analysis, and automated remediation. The project is shaped by +the needs and contributions of its users, and is intended to be both hackable and extensible. + +With **Sysinspect**, you can: + +- Examine and introspect arbitrary systems, provided you have a Model Description of their architecture. +- Detect anomalies and perform root cause analysis using a combination of rules, heuristics, and data-driven methods. +- Track configuration drift and check compliance against a known-good state. +- Automate remediation actions, either as one-offs or as part of a workflow. +- Extend the system with your own modules, handlers, and integrations. +- Run it on Android, embedded Linux environments with questionable tooling (or none at all), or traditional server environments. + +If you enjoy tinkering with system internals, building automation, or just poking around to see how things work, +Sysinspect is meant to be a toolkit you can adapt and extend. Contributions, bug reports, and wild ideas are all +welcome—see the section on contributing for how to get involved. .. toctree:: :maxdepth: 1 @@ -27,13 +47,6 @@ Welcome to the Project Sysinspect! tutorial/module_management -Welcome to Sysinspect: an engine of Anomaly Detection and Root Cause Analysis. -The name consists of two words "system" and "inspection", resulting in "suspicion". - -This engine is indented to perform anomaly detection and root cause analysis on -any system. It is using Model Description as a source of knowledge and a collection of -modules with telemetry data in order to perform various testing scenarios. - Licence ------- diff --git a/docs/modeldescr/actions.rst b/docs/modeldescr/actions.rst index 51226b49..f6fd0598 100644 --- a/docs/modeldescr/actions.rst +++ b/docs/modeldescr/actions.rst @@ -71,6 +71,7 @@ of an action as follows: Below is the description of configuration sections: ``module: namespace`` +^^^^^^^^^^^^^^^^^^^^^ This element assigns the content of an action to a specific module that will process it. Example: @@ -80,6 +81,7 @@ Below is the description of configuration sections: module: sys.proc ``bind: [list]`` +^^^^^^^^^^^^^^^^^ This element binds entities to the action. I.e. an action will process every mentioned entity. Example: @@ -91,6 +93,7 @@ Below is the description of configuration sections: - journald ``state : [map]`` +^^^^^^^^^^^^^^^^^ A configuration group for the particular state. It must be the same ID as state ID in the entities collection. If actions processing the system in a serial fashion without knowing what it is even discovered, then how exactly @@ -104,61 +107,144 @@ Below is the description of configuration sections: the corresponding module aware of the currently processed state. Therefore, in case of the state is requested other than it is currently detected on the device, the module should return **true**. -``opts|options: [list]`` - Options element ``opts`` (or ``options``) specifies flags to the module, in case it is needed. For example, a module - called ``sys.proc`` might have different modes, such as checking if a process at all runs - and do nothing else, or return its PID or owner, even stop it, restart it etc — it depends on - a module. In any case, options would be statically passed in this action. Example: + ``opts|options: [list]`` (optional) - .. code-block:: yaml + Options element ``opts`` (or ``options``) specifies flags to the module, in case it is needed. For example, a module + called ``sys.proc`` might have different modes, such as checking if a process at all runs + and do nothing else, or return its PID or owner, even stop it, restart it etc — it depends on + a module. In any case, options would be statically passed in this action. Example: - opts: - - info + .. code-block:: yaml - The example above is equivalent to a command line expression like this: + opts: + - info - ``some-program --info`` + The example above is equivalent to a command line expression like this: -``args|arguments: key/[list]`` + ``some-program --info`` - The ``args`` (or ``arguments``) element specifies keywords to the module. One **distinct difference** from - a classic keywords is that this is a ``key/[list]`` *(of values)* rather then a ``key/value``. - Example: + ``args|arguments: key/[list]`` (optional) - .. code-block:: yaml + The ``args`` (or ``arguments``) element specifies keywords to the module. One **distinct difference** from + a classic keywords is that this is a ``key/[list]`` *(of values)* rather then a ``key/value``. + Example: - args: - file: - - /var/log/messages + .. code-block:: yaml - The example above is equivalent to a command line expression like this: + args: + file: + - /var/log/messages - ``some-program --file=/var/log/messages`` + The example above is equivalent to a command line expression like this: - .. note:: + ``some-program --file=/var/log/messages`` - Arguments and options are not directly one-to-one transpose of a CLI arguments. - They are just structures in JSON format, those still can be properly interpreted - by a module. + .. note:: - As per note above, if a specific program requires multiple same arguments, this still - can be achieved by grouping them as a list under one argument. For example, if a CLI - equivalent is needed to this: + Arguments and options are not directly one-to-one transpose of a CLI arguments. + They are just structures in JSON format, those still can be properly interpreted + by a module. - ``some-program --file=/var/log/messages --file=/var/log/dmesg`` + As per note above, if a specific program requires multiple same arguments, this still + can be achieved by grouping them as a list under one argument. For example, if a CLI + equivalent is needed to this: - The form above still can be achieved in this form: + ``some-program --file=/var/log/messages --file=/var/log/dmesg`` - .. code-block:: yaml + The form above still can be achieved in this form: + + .. code-block:: yaml + + args: + file: + - /var/log/messages + - /var/log/dmesg + + In this case a module will get a JSON data with ``file`` key and a list of paths, + that can be then translated by a module in whatever required format. + + ``context|ctx [map]`` (if defined) + + Context variable definitions for **documentation** purposes, when they are required by a model description. + They are defined as key/value pairs, where key is the variable name and value is its description. + Example: + + .. code-block:: yaml+jinja + + context: + foo: Some value that will be used to run the module + bar: Some other flag or value for the same reason + + # And then usage of context variables in args: + args: + {% if context.foo is defined %} + something: "context(foo)" + {% endif %} + {% if context.bar is defined %} + another: "context(bar)" + {% endif %} + + Surely, ``context`` does not have to be defined, but then API will not reflect and introspect + the whole model properly, because SysInspect will first render and then examine the model. As it is seen + in the example above, context variables are used in Jinja2 templating. In this case ``{% if %}`` clause + will just cut out a chunk of Model description, rendering impossible to reflect state arguments to the + end user. + + ``conditions|conds: [map]`` (optional) + + Conditions are additional constraints that setting up the environment for a module. + For example, a module might require to run as ``nobody`` user, or it might require + a specific working directory, or it might require a specific amount of memory + or disk space. These conditions are setting up the environment for a module. + Example: + + .. code-block:: yaml + + conditions: # or conds: + uid: 65432 # nobody user + gid: 65432 # nobody group + virtual-memory: 64Mb + + # working directory can be set only if working-disk is defined + working-dir: /tmp + working-disk: 100Mb + + This is important to understand that conditions are not using ``sudo`` mechanism. + Which means, conditions can only limit down the privileges of a module, but + cannot elevate them. For example, if a minion is running as ``nobody`` user, + a module cannot be elevated to ``root`` user. However, if a minion is running as + ``root``, a module surely can be dropped down to ``nobody`` user. + + .. note:: + + Default conditions are transparent, acquiring all privileges of the minion. + That is, ``uid`` and ``gid`` will be the same as the minion is running. + ``working-dir`` will be any current one, ``virtual-memory`` and ``disk`` are + as limited as allowed to the minion. + + Here is the list of available options: + + ``uid`` and ``gid`` + + Numeric values of the user and group respectively. + + ``virtual-memory`` + + Maximum amount of virtual memory a module can allocate. + + ``working-dir`` + + Working directory for a module. + + ``working-disk`` + + Amount of disk space a module can use. + + ``fsize-cap`` - args: - file: - - /var/log/messages - - /var/log/dmesg + Maximum size of a file a module can create. - In this case a module will get a JSON data with ``file`` key and a list of paths, - that can be then translated by a module in whatever required format. Examples of Actions @@ -254,6 +340,12 @@ Another example, showing static data references. Consider the following configur - syslogd state: $: + conditions: + uid: 0 + gid: 0 + virtual-memory: 64Mb + disk: 100Mb + working-dir: /tmp args: # Variable $(foo.bar) always refers to a full path from the document root. - free-disk: "static(entities.syslogd.claims.storage.free)" diff --git a/libsysinspect/Cargo.toml b/libsysinspect/Cargo.toml index f3bffef3..3ff87ac9 100644 --- a/libsysinspect/Cargo.toml +++ b/libsysinspect/Cargo.toml @@ -52,3 +52,4 @@ jsonpath_lib = "0.3.0" jsonpath-rust = "1.0.4" humantime = "2.3.0" humantime-serde = "1.1.1" +libc = "0.2.176" diff --git a/libsysinspect/src/intp/actions.rs b/libsysinspect/src/intp/actions.rs index a3514808..07ebbd6e 100644 --- a/libsysinspect/src/intp/actions.rs +++ b/libsysinspect/src/intp/actions.rs @@ -19,7 +19,11 @@ pub struct ModArgs { #[serde(alias = "args")] arguments: Option>, + #[serde(alias = "ctx")] context: Option>, // Context variables definition for Jinja templates. Used only for model documentation. + + #[serde(alias = "conds")] + conditions: Option>, // Conditions to be met for this state } impl ModArgs { @@ -46,6 +50,11 @@ impl ModArgs { pub fn context(&self) -> IndexMap { self.context.to_owned().unwrap_or_default() } + + /// Get conditions + pub fn conditions(&self) -> IndexMap { + self.conditions.to_owned().unwrap_or_default() + } } #[derive(Debug, Serialize, Deserialize, Default, Clone)] @@ -200,6 +209,7 @@ impl Action { // Setup modcall let mut modcall = ModCall::default().set_state(state).set_module(mpath).set_aid(self.id()).set_eid(eid.to_string()).set_constraints(cst); + // Set module launching arguments for (kw, arg) in &mod_args.args() { let mut arg = arg.to_owned(); if let Ok(Some(func)) = functions::is_function(&arg) { @@ -222,9 +232,16 @@ impl Action { modcall.add_kwargs(kw.to_owned(), arg); } + // Set module launching options for opt in &mod_args.opts() { modcall.add_opt(opt.to_owned()); } + + // Add module launching conditions + for (kw, cond) in mod_args.conditions() { + modcall.add_condition(kw, cond.clone()); + } + self.call = Some(modcall); } else { return Err(SysinspectError::ModelDSLError(format!( diff --git a/libsysinspect/src/intp/actproc/modfinder.rs b/libsysinspect/src/intp/actproc/modfinder.rs index 3044e7fe..b40390f4 100644 --- a/libsysinspect/src/intp/actproc/modfinder.rs +++ b/libsysinspect/src/intp/actproc/modfinder.rs @@ -9,21 +9,39 @@ use crate::{ functions, inspector::get_cfg_sharelib, }, + mdescr::{ + DSL_ACTION_CONDITION_FSZC, DSL_ACTION_CONDITION_GID, DSL_ACTION_CONDITION_UID, DSL_ACTION_CONDITION_VMEM, DSL_ACTION_CONDITION_WDIR, + DSL_ACTION_CONDITION_WDISK, + }, pylang, util::dataconv, }; use core::str; use indexmap::IndexMap; +use nix::unistd::{Gid, setgid, setgroups}; use serde::{Deserialize, Serialize}; use serde_json::json; +use serde_yaml::Value; +use std::{ffi::OsStr, path::Path, process::Child}; use std::{ fmt::Display, - io::Write, + io::{self, Write}, path::PathBuf, process::{Command, Stdio}, vec, }; - +use std::{io::Read, os::unix::process::CommandExt}; + +#[derive(Debug)] +pub struct SpawnSpec<'a> { + pub module: &'a OsStr, // program path/name + pub args: &'a [&'a str], + pub json_in: &'a [u8], // what you write to stdin + pub workdir: &'a str, // your mounted quota dir + pub uid: u32, + pub gid: u32, + pub fsize_cap: u64, // bytes for RLIMIT_FSIZE (0 = no cap) +} #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ModCall { // Action Id @@ -44,6 +62,7 @@ pub struct ModCall { // Module params args: IndexMap, // XXX: Should be String/Value, not String/String! opts: Vec, + conditions: IndexMap, } impl ModCall { @@ -89,6 +108,30 @@ impl ModCall { self } + /// Add a condition + pub fn add_condition(&mut self, cond: String, v: Value) -> &mut Self { + self.conditions.insert(cond, v); + self + } + + /// Get a condition by its name + pub fn get_condition(&self, cond: &str) -> Option<&Value> { + let c = [ + DSL_ACTION_CONDITION_UID, + DSL_ACTION_CONDITION_GID, + DSL_ACTION_CONDITION_VMEM, + DSL_ACTION_CONDITION_FSZC, + DSL_ACTION_CONDITION_WDIR, + DSL_ACTION_CONDITION_WDISK, + ]; + if c.contains(&cond) { + self.conditions.get(cond) + } else { + log::warn!("Module is requesting unknown condition: {}", cond); + None + } + } + /// Set constraints pub fn set_constraints(mut self, cstr: Vec) -> Self { self.constraints = cstr; @@ -286,42 +329,189 @@ impl ModCall { } } + fn to_io(e: E) -> io::Error { + io::Error::other(e.to_string()) + } + + /// Spawn, drop to uid/gid, cap single-file size, write json to stdin, return stdout as String + fn spawn(&self, spec: &SpawnSpec) -> io::Result { + let uid = spec.uid; + let gid = spec.gid; + let fsize_cap = spec.fsize_cap; + + let mut cmd = Command::new(spec.module); + cmd.args(spec.args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped()); + + // Change working dir, if specified + if !spec.workdir.is_empty() && Path::new(spec.workdir).exists() { + cmd.current_dir(spec.workdir); + } + + log::debug!("Setting up environment and privileges: {:?}", cmd); + + unsafe { + cmd.pre_exec(move || { + // Harden defaults + libc::umask(0o077); + + // Drop supplementary groups (must be before setgid/setuid) + setgroups(&[]).map_err(Self::to_io)?; + + // Set primary GID first + setgid(Gid::from_raw(gid)).map_err(Self::to_io)?; + + // Block priv-escalation via setuid binaries + if libc::prctl(libc::PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) != 0 { + log::error!("Failed to set no new privs"); + return Err(io::Error::last_os_error()); + } + + // Cap single file size if requested + if fsize_cap > 0 { + let lim = libc::rlimit { rlim_cur: fsize_cap, rlim_max: fsize_cap }; + let rc = libc::setrlimit(libc::RLIMIT_FSIZE, &lim as *const _); + if rc != 0 { + log::error!("Failed to set file size limit"); + return Err(io::Error::last_os_error()); + } + } + + // Drop to UID (ruid/euid/suid are real/effective/saved respectively) + let rc = libc::setresuid(uid, uid, uid); + if rc != 0 { + log::error!("Failed to drop privileges to UID {}", uid); + return Err(io::Error::last_os_error()); + } + + Ok(()) + }) + }; + + log::debug!("Spawning child process: {:?}", cmd); + let mut child: Child = cmd.spawn()?; + + log::debug!("Writing JSON to stdin: {}", String::from_utf8_lossy(spec.json_in)); + + if let Some(mut sin) = child.stdin.take() { + sin.write_all(spec.json_in)?; + // Necessary to close stdin so child can see EOF + drop(sin); + } + + let mut so = child.stdout.take().unwrap(); + let mut se = child.stderr.take().unwrap(); + + let t_out = std::thread::spawn(move || { + let mut s = String::new(); + let _ = so.read_to_string(&mut s); + s + }); + let t_err = std::thread::spawn(move || { + let mut b = Vec::new(); + let _ = se.read_to_end(&mut b); + b + }); + + let status = child.wait()?; + let out = t_out.join().map_err(|_| io::Error::other("Failed to join STDOUT thread"))?; + let err = t_err.join().map_err(|_| io::Error::other("Failed to join STDERR thread"))?; + + if !status.success() { + return Err(io::Error::other(format!("child exit {status:?}; stderr: {}", String::from_utf8_lossy(&err)))); + } + Ok(out) + } + /// Runs native external module fn run_native_module(&self) -> Result, SysinspectError> { log::debug!("Calling native module: {}", self.module.as_os_str().to_str().unwrap_or_default()); log::debug!("Params: {}", self.params_json()); - - match Command::new(&self.module).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn() { - Ok(mut p) => { - // Send options - if let Some(mut stdin) = p.stdin.take() - && let Err(err) = stdin.write_all(self.params_json().as_bytes()) { - return Err(SysinspectError::ModuleError(format!("Error while communicating with the module: {err}"))); - } - - // Get the output - if let Ok(out) = p.wait_with_output() { - match str::from_utf8(&out.stdout) { - Ok(out) => match serde_json::from_str::(out) { - Ok(r) => Ok(Some(ActionResponse::new( + log::debug!("Opts: {:?}", self.opts); + log::debug!("Conditions: {:?}", self.conditions); + + let muid = unsafe { libc::getuid() }; + let mgid = unsafe { libc::getgid() }; + + if muid == 0 && mgid == 0 { + let binding = self.params_json(); + // XXX: fsize-cap, working dir/disk and vmem still needs to be implemented + let spec = SpawnSpec { + module: self.module.as_os_str(), + args: &[], + json_in: binding.as_bytes(), + workdir: self.get_condition(DSL_ACTION_CONDITION_WDIR).and_then(|v| v.as_str()).unwrap_or(""), + uid: self.get_condition(DSL_ACTION_CONDITION_UID).and_then(|v| v.as_u64()).map(|v| v as u32).unwrap_or(muid), + gid: self.get_condition(DSL_ACTION_CONDITION_GID).and_then(|v| v.as_u64()).map(|v| v as u32).unwrap_or(mgid), + fsize_cap: 10 * 1024 * 1024, + }; + + log::debug!("Spawning module with spec: {:?}", spec); + + match Self::spawn(self, &spec) { + Ok(out) => match str::from_utf8(out.as_bytes()) { + Ok(out) => match serde_json::from_str::(out) { + Ok(r) => { + let mut data = r.clone(); + data.add_data("run-uid", json!(spec.uid)); + data.add_data("run-gid", json!(spec.gid)); + + Ok(Some(ActionResponse::new( self.eid.to_owned(), self.aid.to_owned(), self.state.to_owned(), - r.clone(), + data, self.eval_constraints(&r), - ))), - Err(e) => { - log::debug!("STDOUT: {out}"); - Err(SysinspectError::ModuleError(format!("JSON error: {e}"))) - } - }, - Err(err) => Err(SysinspectError::ModuleError(format!("Error obtaining the output: {err}"))), + ))) + } + Err(e) => { + log::debug!("STDOUT: {out}"); + Err(SysinspectError::ModuleError(format!("JSON error: {e}"))) + } + }, + Err(err) => Err(SysinspectError::ModuleError(format!("Error obtaining the output: {err}"))), + }, + Err(err) => Err(SysinspectError::ModuleError(format!("Error calling module: {err}"))), + } + } else { + log::debug!("Spawning module with default privileges"); + match Command::new(&self.module).stdin(Stdio::piped()).stdout(Stdio::piped()).spawn() { + Ok(mut p) => { + // Send options + if let Some(mut stdin) = p.stdin.take() + && let Err(err) = stdin.write_all(self.params_json().as_bytes()) + { + return Err(SysinspectError::ModuleError(format!("Error while communicating with the module: {err}"))); + } + + // Get the output + if let Ok(out) = p.wait_with_output() { + match str::from_utf8(&out.stdout) { + Ok(out) => match serde_json::from_str::(out) { + Ok(r) => { + let mut data = r.clone(); + data.add_data("run-uid", json!(muid)); + data.add_data("run-gid", json!(mgid)); + Ok(Some(ActionResponse::new( + self.eid.to_owned(), + self.aid.to_owned(), + self.state.to_owned(), + data, + self.eval_constraints(&r), + ))) + } + Err(e) => { + log::debug!("STDOUT: {out}"); + Err(SysinspectError::ModuleError(format!("JSON error: {e}"))) + } + }, + Err(err) => Err(SysinspectError::ModuleError(format!("Error obtaining the output: {err}"))), + } + } else { + Err(SysinspectError::ModuleError("Module returned no output".to_string())) } - } else { - Err(SysinspectError::ModuleError("Module returned no output".to_string())) } + Err(err) => Err(SysinspectError::ModuleError(format!("Error calling module: {err}"))), } - Err(err) => Err(SysinspectError::ModuleError(format!("Error calling module: {err}"))), } } @@ -356,6 +546,7 @@ impl Default for ModCall { args: IndexMap::default(), opts: Vec::default(), constraints: Vec::default(), + conditions: IndexMap::default(), } } } diff --git a/libsysinspect/src/intp/actproc/response.rs b/libsysinspect/src/intp/actproc/response.rs index 258400e5..99bae8c0 100644 --- a/libsysinspect/src/intp/actproc/response.rs +++ b/libsysinspect/src/intp/actproc/response.rs @@ -4,7 +4,7 @@ use crate::{ }; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; -use serde_json::Value; +use serde_json::{Map, Value}; /// This struct is a future carrier of tracability. /// Currently only a single string log message. @@ -125,7 +125,7 @@ pub struct ActionModResponse { message: String, // Arbitrary payload data - data: Option, + data: Option, } impl ActionModResponse { @@ -150,6 +150,26 @@ impl ActionModResponse { pub fn data(&self) -> Option { self.data.to_owned() } + + /// Add or merge a key-value pair into the data object. + pub fn add_data(&mut self, key: &str, v: Value) { + match &mut self.data { + Some(Value::Object(map)) => { + map.insert(key.to_string(), v); + } + Some(_) => { + // If data exists but is not an object, replace it with an object + let mut map = Map::new(); + map.insert(key.to_string(), v); + self.data = Some(Value::Object(map)); + } + None => { + let mut map = Map::new(); + map.insert(key.to_string(), v); + self.data = Some(Value::Object(map)); + } + } + } } #[derive(Debug, Default, Clone, Serialize, Deserialize)] @@ -234,9 +254,10 @@ impl ActionResponse { // If explicitly specified and already matching for expr in self.constraints.expressions() { if let Some(ovr_evt_id) = expr.get_event_id() - && evt_id.eq(&ovr_evt_id) { - return true; - } + && evt_id.eq(&ovr_evt_id) + { + return true; + } } let p_eid = evt_id.split('/').map(|s| s.trim()).collect::>(); diff --git a/libsysinspect/src/mdescr/mod.rs b/libsysinspect/src/mdescr/mod.rs index 3ae97ff3..500a33c1 100644 --- a/libsysinspect/src/mdescr/mod.rs +++ b/libsysinspect/src/mdescr/mod.rs @@ -16,3 +16,10 @@ pub static DSL_IDX_CFG: &str = "config"; // This one belongs to the configuration, but is defined // outside in the tree, as there migt be really many of those. pub static DSL_IDX_EVENTS_CFG: &str = "events"; + +pub static DSL_ACTION_CONDITION_UID: &str = "uid"; +pub static DSL_ACTION_CONDITION_GID: &str = "gid"; +pub static DSL_ACTION_CONDITION_VMEM: &str = "virtual-memory"; +pub static DSL_ACTION_CONDITION_WDIR: &str = "working-dir"; +pub static DSL_ACTION_CONDITION_WDISK: &str = "working-disk"; +pub static DSL_ACTION_CONDITION_FSZC: &str = "fsize-cap";