Skip to main content

whyno_core/state/
mac.rs

1//! MAC (mandatory access control) state types for `SELinux` and `AppArmor`.
2//!
3//! Pre-gathered at query time by the gathering layer. Check pipeline
4//! consumes these types without any I/O.
5
6use serde::Serialize;
7
8use crate::state::Probe;
9
10/// `SELinux` kernel enforcement mode.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
12#[non_exhaustive]
13pub enum SeLinuxMode {
14    /// Policy is actively enforced; denials block access.
15    Enforcing,
16    /// Policy is evaluated but denials are logged, not enforced.
17    Permissive,
18    /// `SELinux` is compiled out or disabled at boot.
19    Disabled,
20}
21
22/// Pre-gathered `SELinux` state for a permission query.
23#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
24pub struct SeLinuxState {
25    /// Kernel enforcement mode at gather time.
26    pub mode: SeLinuxMode,
27    /// Subject security context, e.g. `"unconfined_u:unconfined_r:unconfined_t:s0"`.
28    pub subject_ctx: String,
29    /// Target file security context, e.g. `"system_u:object_r:httpd_sys_content_t:s0"`.
30    pub target_ctx: String,
31    /// AVC access decision pre-computed during gathering.
32    pub access_allowed: bool,
33}
34
35/// Pre-gathered `AppArmor` state for a permission query.
36#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
37pub struct AppArmorState {
38    /// Profile label as read from procfs, e.g. `"nginx (enforce)"` or `"unconfined"`.
39    pub profile_label: String,
40}
41
42/// MAC layer state gathered before the check pipeline runs.
43///
44/// Both probes default to `Probe::Unknown` when the corresponding
45/// subsystem is absent or unreadable.
46#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
47pub struct MacState {
48    /// `SELinux` probe result.
49    pub selinux: Probe<SeLinuxState>,
50    /// `AppArmor` probe result.
51    pub apparmor: Probe<AppArmorState>,
52}
53
54impl Default for MacState {
55    fn default() -> Self {
56        Self {
57            selinux: Probe::Unknown,
58            apparmor: Probe::Unknown,
59        }
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn mac_state_default_has_unknown_probes() {
69        let state = MacState::default();
70        assert_eq!(state.selinux, Probe::Unknown);
71        assert_eq!(state.apparmor, Probe::Unknown);
72    }
73
74    #[test]
75    fn selinux_state_roundtrips_fields() {
76        let s = SeLinuxState {
77            mode: SeLinuxMode::Enforcing,
78            subject_ctx: "unconfined_u:unconfined_r:unconfined_t:s0".into(),
79            target_ctx: "system_u:object_r:httpd_sys_content_t:s0".into(),
80            access_allowed: true,
81        };
82        assert_eq!(s.mode, SeLinuxMode::Enforcing);
83        assert!(s.access_allowed);
84    }
85
86    #[test]
87    fn apparmor_state_roundtrips_label() {
88        let a = AppArmorState {
89            profile_label: "nginx (enforce)".into(),
90        };
91        assert_eq!(a.profile_label, "nginx (enforce)");
92    }
93}