Skip to main content

cageforge_policy/
access.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Shared access results for the filesystem and network policy modules.
4//!
5//! [`crate::AccessMode`] expresses a filesystem rule, while
6//! [`crate::FilesystemDecision`] preserves whether a result is local or
7//! externally enforced. The network module has its own [`crate::NetworkDecision`]
8//! because connection authorization also carries an exact socket address.
9
10/// Filesystem access requested or granted for a policy entry.
11///
12/// This type intentionally does not implement [`Ord`]. Use
13/// [`Self::most_restrictive`] when combining access modes; declaration order
14/// is not an authorization rule.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
16pub enum AccessMode {
17    /// Permit reads but not modifications.
18    Read,
19    /// Permit reads and modifications.
20    Write,
21    /// Permit neither reads nor modifications.
22    Deny,
23}
24
25/// The result of evaluating a filesystem request against a policy.
26///
27/// `ExternallyEnforced` is intentionally distinct from [`AccessMode::Deny`].
28/// It tells a backend that Cageforge does not make the local decision because
29/// another trusted sandbox owns the filesystem boundary.
30///
31/// This type intentionally does not implement [`Ord`]. Local access decisions
32/// and external ownership are not one total permission ordering; callers must
33/// handle `ExternallyEnforced` explicitly instead of combining decisions with
34/// `min` or `max`.
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub enum FilesystemDecision {
37    /// Permit reads but not modifications.
38    Read,
39    /// Permit reads and modifications.
40    Write,
41    /// Permit neither reads nor modifications.
42    Deny,
43    /// Defer enforcement to the trusted external sandbox.
44    ExternallyEnforced,
45}
46
47impl AccessMode {
48    /// Returns whether this mode permits a read.
49    pub const fn can_read(self) -> bool {
50        matches!(self, Self::Read | Self::Write)
51    }
52
53    /// Returns whether this mode permits a modification.
54    pub const fn can_write(self) -> bool {
55        matches!(self, Self::Write)
56    }
57
58    /// Returns the more restrictive capability for two conflicting grants.
59    ///
60    /// The order is `Deny` over `Read` over `Write`. Explicit profile
61    /// replacement is a separate operation owned by the config resolver.
62    pub const fn most_restrictive(self, other: Self) -> Self {
63        match (self, other) {
64            (Self::Deny, _) | (_, Self::Deny) => Self::Deny,
65            (Self::Read, _) | (_, Self::Read) => Self::Read,
66            (Self::Write, Self::Write) => Self::Write,
67        }
68    }
69
70    /// Returns whether this grant satisfies a requested access mode.
71    pub const fn permits(self, requested: Self) -> bool {
72        matches!(
73            (self, requested),
74            (Self::Write, Self::Read | Self::Write)
75                | (Self::Read, Self::Read)
76                | (Self::Deny, Self::Deny)
77        )
78    }
79}
80
81impl FilesystemDecision {
82    /// Returns the local access mode, or `None` when another sandbox enforces it.
83    pub const fn as_access_mode(self) -> Option<AccessMode> {
84        match self {
85            Self::Read => Some(AccessMode::Read),
86            Self::Write => Some(AccessMode::Write),
87            Self::Deny => Some(AccessMode::Deny),
88            Self::ExternallyEnforced => None,
89        }
90    }
91
92    /// Returns whether local filesystem enforcement is delegated elsewhere.
93    pub const fn is_externally_enforced(self) -> bool {
94        matches!(self, Self::ExternallyEnforced)
95    }
96}
97
98impl From<AccessMode> for FilesystemDecision {
99    fn from(access: AccessMode) -> Self {
100        match access {
101            AccessMode::Read => Self::Read,
102            AccessMode::Write => Self::Write,
103            AccessMode::Deny => Self::Deny,
104        }
105    }
106}