Skip to main content

sysknife_daemon/actions/
auditd.rs

1//! auditd file-watch rule actions.
2//!
3//! `GetAuditRules` (`auditctl -l`) is read-only. `AddAuditRule`/`RemoveAuditRule`
4//! delegate to the root-owned helper `/usr/lib/sysknife/audit-edit`, which writes
5//! a persistent rule under `/etc/audit/rules.d/` and loads it with `augenrules`.
6//!
7//! Scope: file-WATCH rules only (`-w <path> -p <perms> -k <key>`). Syscall rules
8//! are intentionally out of scope (large injection surface).
9//!
10//! Network-gated: the `auditd` package is not on the base cloud image, so the
11//! `auditctl`/`augenrules` behaviour could not be live-validated in the sandbox
12//! — only the helper's rule-file write was. See the helper header.
13
14use super::{command_mechanism, ActionSpec};
15use sysknife_types::RiskLevel;
16
17const HELPER: &str = "/usr/lib/sysknife/audit-edit";
18
19pub fn specs() -> Vec<ActionSpec> {
20    vec![
21        get_audit_rules(),
22        add_audit_rule("/etc/passwd", "wa", "passwd-watch"),
23        remove_audit_rule("passwd-watch"),
24    ]
25}
26
27/// List the loaded audit rules (`auditctl -l`). Read-only.
28pub fn get_audit_rules() -> ActionSpec {
29    ActionSpec {
30        action_name: "GetAuditRules",
31        mechanism: command_mechanism("auditctl", ["-l"]),
32        risk_level: RiskLevel::Low,
33        reboot_required: false,
34        rollback_available: false,
35    }
36}
37
38/// Add a persistent file-watch rule via the helper.
39pub fn add_audit_rule(path: &str, perms: &str, key: &str) -> ActionSpec {
40    ActionSpec {
41        action_name: "AddAuditRule",
42        mechanism: command_mechanism(
43            "sudo",
44            [
45                HELPER, "--op", "add", "--path", path, "--perms", perms, "--key", key,
46            ],
47        ),
48        risk_level: RiskLevel::Medium,
49        reboot_required: false,
50        rollback_available: false,
51    }
52}
53
54/// Remove a SysKnife-managed audit rule by key.
55pub fn remove_audit_rule(key: &str) -> ActionSpec {
56    ActionSpec {
57        action_name: "RemoveAuditRule",
58        mechanism: command_mechanism("sudo", [HELPER, "--op", "remove", "--key", key]),
59        risk_level: RiskLevel::High,
60        reboot_required: false,
61        rollback_available: false,
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68    use crate::actions::ActionMechanism;
69
70    fn args_of(spec: &ActionSpec) -> (&'static str, Vec<String>) {
71        match &spec.mechanism {
72            ActionMechanism::Command { program, args } => (program, args.clone()),
73            other => panic!("expected Command, got {other:?}"),
74        }
75    }
76
77    #[test]
78    fn get_rules_is_read_only() {
79        let (program, args) = args_of(&get_audit_rules());
80        assert_eq!(program, "auditctl");
81        assert_eq!(args, vec!["-l"]);
82        assert_eq!(get_audit_rules().risk_level, RiskLevel::Low);
83    }
84
85    #[test]
86    fn add_rule_shape() {
87        let (program, args) = args_of(&add_audit_rule("/etc/passwd", "wa", "pw"));
88        assert_eq!(program, "sudo");
89        assert_eq!(
90            args,
91            vec![
92                HELPER,
93                "--op",
94                "add",
95                "--path",
96                "/etc/passwd",
97                "--perms",
98                "wa",
99                "--key",
100                "pw"
101            ]
102        );
103        assert_eq!(add_audit_rule("/x", "r", "k").risk_level, RiskLevel::Medium);
104    }
105
106    #[test]
107    fn remove_rule_shape() {
108        let (_, args) = args_of(&remove_audit_rule("pw"));
109        assert_eq!(args, vec![HELPER, "--op", "remove", "--key", "pw"]);
110    }
111}