Skip to main content

sh_layer4/plugin_loader/
capabilities.rs

1//! Plugin Capability-Based Security Model
2//!
3//! Defines permissions for plugin operations.
4
5use serde::{Deserialize, Serialize};
6use std::collections::HashSet;
7
8/// Plugin capability/permission
9#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
10pub enum Capability {
11    /// File system read access
12    FsRead,
13    /// File system write access
14    FsWrite,
15    /// Network outbound access
16    NetworkOut,
17    /// Process execution
18    ProcessExec,
19    /// Environment variable read
20    EnvRead,
21    /// Environment variable write
22    EnvWrite,
23    /// Access to host clock
24    Clock,
25    /// Random number generation
26    Random,
27    /// Memory limit in bytes
28    MemoryLimit(u64),
29    /// CPU time limit in milliseconds
30    CpuLimit(u64),
31}
32
33/// Capability set with allow/deny lists
34#[derive(Debug, Clone, Default)]
35pub struct CapabilitySet {
36    /// Allowed capabilities
37    pub allowed: HashSet<Capability>,
38    /// Explicitly denied (overrides allowed)
39    pub denied: HashSet<Capability>,
40}
41
42impl CapabilitySet {
43    /// Create empty capability set (no permissions)
44    pub fn new() -> Self {
45        Self::default()
46    }
47
48    /// Create unrestricted capability set (all permissions)
49    pub fn unrestricted() -> Self {
50        let mut allowed = HashSet::new();
51        allowed.insert(Capability::FsRead);
52        allowed.insert(Capability::FsWrite);
53        allowed.insert(Capability::NetworkOut);
54        allowed.insert(Capability::ProcessExec);
55        allowed.insert(Capability::EnvRead);
56        allowed.insert(Capability::EnvWrite);
57        allowed.insert(Capability::Clock);
58        allowed.insert(Capability::Random);
59        Self {
60            allowed,
61            denied: HashSet::new(),
62        }
63    }
64
65    /// Create sandboxed capability set (minimal permissions)
66    pub fn sandboxed() -> Self {
67        let mut allowed = HashSet::new();
68        allowed.insert(Capability::Clock);
69        allowed.insert(Capability::Random);
70        allowed.insert(Capability::MemoryLimit(16 * 1024 * 1024)); // 16MB
71        allowed.insert(Capability::CpuLimit(5000)); // 5 seconds
72        Self {
73            allowed,
74            denied: HashSet::new(),
75        }
76    }
77
78    /// Add a capability to allowed set
79    pub fn allow(&mut self, cap: Capability) -> &mut Self {
80        self.allowed.insert(cap.clone());
81        self.denied.remove(&cap);
82        self
83    }
84
85    /// Add a capability to denied set
86    pub fn deny(&mut self, cap: Capability) -> &mut Self {
87        self.denied.insert(cap.clone());
88        self.allowed.remove(&cap);
89        self
90    }
91
92    /// Check if capability is granted
93    pub fn check(&self, cap: &Capability) -> bool {
94        self.allowed.contains(cap) && !self.denied.contains(cap)
95    }
96
97    /// Merge with another capability set
98    pub fn merge(&mut self, other: &CapabilitySet) -> &mut Self {
99        for cap in &other.allowed {
100            if !self.denied.contains(cap) {
101                self.allowed.insert(cap.clone());
102            }
103        }
104        for cap in &other.denied {
105            self.allowed.remove(cap);
106            self.denied.insert(cap.clone());
107        }
108        self
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn test_empty_capability_set() {
118        let caps = CapabilitySet::new();
119        assert!(!caps.check(&Capability::FsRead));
120        assert!(!caps.check(&Capability::NetworkOut));
121    }
122
123    #[test]
124    fn test_unrestricted_capability_set() {
125        let caps = CapabilitySet::unrestricted();
126        assert!(caps.check(&Capability::FsRead));
127        assert!(caps.check(&Capability::NetworkOut));
128        assert!(caps.check(&Capability::ProcessExec));
129    }
130
131    #[test]
132    fn test_sandboxed_capability_set() {
133        let caps = CapabilitySet::sandboxed();
134        assert!(!caps.check(&Capability::FsRead));
135        assert!(!caps.check(&Capability::FsWrite));
136        assert!(caps.check(&Capability::Clock));
137        assert!(caps.check(&Capability::Random));
138    }
139
140    #[test]
141    fn test_deny_overrides_allow() {
142        let mut caps = CapabilitySet::unrestricted();
143        caps.deny(Capability::FsWrite);
144        assert!(!caps.check(&Capability::FsWrite));
145        assert!(caps.check(&Capability::FsRead));
146    }
147
148    #[test]
149    fn test_capability_merge() {
150        let mut caps1 = CapabilitySet::new();
151        caps1.allow(Capability::FsRead);
152
153        let caps2 = CapabilitySet::sandboxed();
154
155        caps1.merge(&caps2);
156        assert!(caps1.check(&Capability::FsRead));
157        assert!(caps1.check(&Capability::Clock));
158    }
159}