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}