Skip to main content

cageforge_policy_compose/
lowering.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! Immutable constraint views for native backend lowering.
4//!
5//! The effective decision APIs are sufficient for dynamic checks, but a
6//! native backend also needs the concrete rules, protected paths, and matcher
7//! settings from every constraint layer when it builds an operating-system
8//! sandbox. These views expose all layers together without exposing a single
9//! requested or ceiling policy as an interchangeable enforcement value.
10
11use std::num::NonZeroUsize;
12
13use cageforge_policy::{
14    DomainMode, DomainRule, FilesystemMode, FilesystemPolicy, FilesystemRule, LocalIpcRule,
15    LocalNetworkAccess, NetworkMode, NetworkPolicy, UnixSocketMode, UnixSocketRule,
16};
17
18/// The complete filesystem constraint set required by one effective result.
19///
20/// Every layer returned by [`Self::layers`] is mandatory input to lowering.
21/// A backend must enforce the conjunction of all layers; treating one layer
22/// as an alternative policy would widen the effective result.
23#[derive(Debug, Clone, Copy)]
24pub struct EffectiveFilesystemLowering<'a> {
25    layers: [&'a FilesystemPolicy; 2],
26    glob_scan_max_depth: Option<NonZeroUsize>,
27}
28
29/// One immutable filesystem constraint layer in an effective lowering view.
30#[derive(Debug, Clone, Copy)]
31pub struct EffectiveFilesystemLayer<'a> {
32    policy: &'a FilesystemPolicy,
33}
34
35/// The complete network constraint set required by one effective result.
36///
37/// Every layer returned by [`Self::layers`] is mandatory input to lowering.
38/// Runtime connections must still use the exact-target authorization methods
39/// after lowering; these rules are not themselves socket authorization.
40#[derive(Debug, Clone, Copy)]
41pub struct EffectiveNetworkLowering<'a> {
42    layers: [&'a NetworkPolicy; 2],
43}
44
45/// One immutable network constraint layer in an effective lowering view.
46#[derive(Debug, Clone, Copy)]
47pub struct EffectiveNetworkLayer<'a> {
48    policy: &'a NetworkPolicy,
49}
50
51impl<'a> EffectiveFilesystemLowering<'a> {
52    pub(crate) fn new(
53        requested: &'a FilesystemPolicy,
54        ceiling: &'a FilesystemPolicy,
55        glob_scan_max_depth: Option<NonZeroUsize>,
56    ) -> Self {
57        Self {
58            layers: [requested, ceiling],
59            glob_scan_max_depth,
60        }
61    }
62
63    /// Returns every filesystem constraint layer that must be enforced.
64    ///
65    /// The iterator always contains both composition inputs, including when
66    /// one of them is unrestricted. It is intentionally the only way to
67    /// inspect lowering rules, so callers receive the complete constraint set
68    /// instead of choosing a requested or ceiling side through a public
69    /// accessor.
70    pub fn layers(&self) -> impl ExactSizeIterator<Item = EffectiveFilesystemLayer<'a>> + '_ {
71        self.layers.into_iter().map(EffectiveFilesystemLayer::new)
72    }
73
74    /// Returns the conservative glob scan depth required by all layers.
75    pub const fn glob_scan_max_depth(&self) -> Option<NonZeroUsize> {
76        self.glob_scan_max_depth
77    }
78}
79
80impl<'a> EffectiveFilesystemLayer<'a> {
81    fn new(policy: &'a FilesystemPolicy) -> Self {
82        Self { policy }
83    }
84
85    /// Returns this layer's enforcement ownership mode.
86    pub const fn mode(&self) -> FilesystemMode {
87        self.policy.mode()
88    }
89
90    /// Returns all validated rules in this constraint layer.
91    pub fn entries(&self) -> &[FilesystemRule] {
92        self.policy.entries()
93    }
94
95    /// Returns the layer's configured glob scan depth.
96    pub const fn glob_scan_max_depth(&self) -> Option<NonZeroUsize> {
97        self.policy.glob_scan_max_depth()
98    }
99
100    /// Returns all protected relative paths in this constraint layer.
101    pub fn protected_relative_paths(&self) -> &[std::path::PathBuf] {
102        self.policy.protected_relative_paths()
103    }
104}
105
106impl<'a> EffectiveNetworkLowering<'a> {
107    pub(crate) fn new(requested: &'a NetworkPolicy, ceiling: &'a NetworkPolicy) -> Self {
108        Self {
109            layers: [requested, ceiling],
110        }
111    }
112
113    /// Returns every network constraint layer that must be enforced.
114    pub fn layers(&self) -> impl ExactSizeIterator<Item = EffectiveNetworkLayer<'a>> + '_ {
115        self.layers.into_iter().map(EffectiveNetworkLayer::new)
116    }
117}
118
119impl<'a> EffectiveNetworkLayer<'a> {
120    fn new(policy: &'a NetworkPolicy) -> Self {
121        Self { policy }
122    }
123
124    /// Returns this layer's enforcement ownership mode.
125    pub const fn mode(&self) -> NetworkMode {
126        self.policy.mode()
127    }
128
129    /// Returns the default behavior for unmatched domains.
130    pub const fn domain_mode(&self) -> DomainMode {
131        self.policy.domain_mode()
132    }
133
134    /// Returns the default behavior for unmatched Unix socket paths.
135    pub const fn unix_socket_mode(&self) -> UnixSocketMode {
136        self.policy.unix_socket_mode()
137    }
138
139    /// Returns the layer's local-address restriction mode.
140    pub const fn local_network_access(&self) -> LocalNetworkAccess {
141        self.policy.local_network_access()
142    }
143
144    /// Returns all validated domain rules in this constraint layer.
145    pub fn domains(&self) -> &[DomainRule] {
146        self.policy.domains()
147    }
148
149    /// Returns all validated Unix socket rules in this constraint layer.
150    pub fn unix_sockets(&self) -> &[UnixSocketRule] {
151        self.policy.unix_sockets()
152    }
153
154    /// Returns all typed local-IPC rules in this constraint layer.
155    pub fn local_ipc(&self) -> &[LocalIpcRule] {
156        self.policy.local_ipc()
157    }
158}