whyno-core 0.5.0

Permission check pipeline, fix engine, and state types
Documentation
//! MAC (mandatory access control) state types for `SELinux` and `AppArmor`.
//!
//! Pre-gathered at query time by the gathering layer. Check pipeline
//! consumes these types without any I/O.

use serde::Serialize;

use crate::state::Probe;

/// `SELinux` kernel enforcement mode.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
#[non_exhaustive]
pub enum SeLinuxMode {
    /// Policy is actively enforced; denials block access.
    Enforcing,
    /// Policy is evaluated but denials are logged, not enforced.
    Permissive,
    /// `SELinux` is compiled out or disabled at boot.
    Disabled,
}

/// Pre-gathered `SELinux` state for a permission query.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct SeLinuxState {
    /// Kernel enforcement mode at gather time.
    pub mode: SeLinuxMode,
    /// Subject security context, e.g. `"unconfined_u:unconfined_r:unconfined_t:s0"`.
    pub subject_ctx: String,
    /// Target file security context, e.g. `"system_u:object_r:httpd_sys_content_t:s0"`.
    pub target_ctx: String,
    /// AVC access decision pre-computed during gathering.
    pub access_allowed: bool,
}

/// Pre-gathered `AppArmor` state for a permission query.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct AppArmorState {
    /// Profile label as read from procfs, e.g. `"nginx (enforce)"` or `"unconfined"`.
    pub profile_label: String,
}

/// MAC layer state gathered before the check pipeline runs.
///
/// Both probes default to `Probe::Unknown` when the corresponding
/// subsystem is absent or unreadable.
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct MacState {
    /// `SELinux` probe result.
    pub selinux: Probe<SeLinuxState>,
    /// `AppArmor` probe result.
    pub apparmor: Probe<AppArmorState>,
}

impl Default for MacState {
    fn default() -> Self {
        Self {
            selinux: Probe::Unknown,
            apparmor: Probe::Unknown,
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn mac_state_default_has_unknown_probes() {
        let state = MacState::default();
        assert_eq!(state.selinux, Probe::Unknown);
        assert_eq!(state.apparmor, Probe::Unknown);
    }

    #[test]
    fn selinux_state_roundtrips_fields() {
        let s = SeLinuxState {
            mode: SeLinuxMode::Enforcing,
            subject_ctx: "unconfined_u:unconfined_r:unconfined_t:s0".into(),
            target_ctx: "system_u:object_r:httpd_sys_content_t:s0".into(),
            access_allowed: true,
        };
        assert_eq!(s.mode, SeLinuxMode::Enforcing);
        assert!(s.access_allowed);
    }

    #[test]
    fn apparmor_state_roundtrips_label() {
        let a = AppArmorState {
            profile_label: "nginx (enforce)".into(),
        };
        assert_eq!(a.profile_label, "nginx (enforce)");
    }
}