1use crate::error::{NucleusError, Result};
22use crate::security::{CapabilityManager, CapabilitySets};
23use caps::Capability;
24use serde::Deserialize;
25use tracing::info;
26
27#[derive(Debug, Clone, Deserialize)]
29pub struct CapsPolicy {
30 #[serde(default)]
32 pub bounding: CapSetPolicy,
33
34 #[serde(default)]
36 pub ambient: CapSetPolicy,
37
38 #[serde(default)]
40 pub effective: CapSetPolicy,
41
42 #[serde(default)]
44 pub inheritable: CapSetPolicy,
45}
46
47#[derive(Debug, Clone, Deserialize, Default)]
49pub struct CapSetPolicy {
50 #[serde(default)]
53 pub keep: Vec<String>,
54}
55
56impl CapsPolicy {
57 pub fn apply(&self, mgr: &mut CapabilityManager) -> Result<()> {
62 let sets = self.resolve_sets()?;
63
64 if sets.bounding.is_empty()
65 && sets.permitted.is_empty()
66 && sets.effective.is_empty()
67 && sets.inheritable.is_empty()
68 && sets.ambient.is_empty()
69 {
70 info!("Capability policy: drop all");
71 mgr.drop_all()
72 } else {
73 info!("Capability policy: applying explicit sets {:?}", sets);
74 mgr.apply_sets(&sets)
75 }
76 }
77
78 fn resolve_sets(&self) -> Result<CapabilitySets> {
79 let bounding = resolve_cap_list(&self.bounding.keep)?;
80 let effective = resolve_cap_list(&self.effective.keep)?;
81 let ambient = resolve_cap_list(&self.ambient.keep)?;
82 let mut inheritable = resolve_cap_list(&self.inheritable.keep)?;
83 extend_unique(&mut inheritable, &ambient);
84
85 let mut permitted = Vec::new();
86 extend_unique(&mut permitted, &effective);
87 extend_unique(&mut permitted, &inheritable);
88 extend_unique(&mut permitted, &ambient);
89
90 Ok(CapabilitySets {
91 bounding,
92 permitted,
93 effective,
94 inheritable,
95 ambient,
96 })
97 }
98
99 #[cfg(test)]
101 fn resolve_keep_set(&self) -> Result<Vec<Capability>> {
102 let sets = self.resolve_sets()?;
103 let mut caps = Vec::new();
104 extend_unique(&mut caps, &sets.bounding);
105 extend_unique(&mut caps, &sets.permitted);
106 extend_unique(&mut caps, &sets.effective);
107 extend_unique(&mut caps, &sets.inheritable);
108 extend_unique(&mut caps, &sets.ambient);
109 Ok(caps)
110 }
111}
112
113fn resolve_cap_list(names: &[String]) -> Result<Vec<Capability>> {
114 let mut caps = Vec::new();
115 for name in names {
116 let cap = parse_capability_name(name)?;
117 if !caps.contains(&cap) {
118 caps.push(cap);
119 }
120 }
121 Ok(caps)
122}
123
124fn extend_unique(dst: &mut Vec<Capability>, src: &[Capability]) {
125 for &cap in src {
126 if !dst.contains(&cap) {
127 dst.push(cap);
128 }
129 }
130}
131
132fn parse_capability_name(name: &str) -> Result<Capability> {
137 let normalized = name.strip_prefix("CAP_").unwrap_or(name);
138 match normalized {
139 "CHOWN" => Ok(Capability::CAP_CHOWN),
140 "DAC_OVERRIDE" => Ok(Capability::CAP_DAC_OVERRIDE),
141 "DAC_READ_SEARCH" => Ok(Capability::CAP_DAC_READ_SEARCH),
142 "FOWNER" => Ok(Capability::CAP_FOWNER),
143 "FSETID" => Ok(Capability::CAP_FSETID),
144 "KILL" => Ok(Capability::CAP_KILL),
145 "SETGID" => Ok(Capability::CAP_SETGID),
146 "SETUID" => Ok(Capability::CAP_SETUID),
147 "SETPCAP" => Ok(Capability::CAP_SETPCAP),
148 "LINUX_IMMUTABLE" => Ok(Capability::CAP_LINUX_IMMUTABLE),
149 "NET_BIND_SERVICE" => Ok(Capability::CAP_NET_BIND_SERVICE),
150 "NET_BROADCAST" => Ok(Capability::CAP_NET_BROADCAST),
151 "NET_ADMIN" => Ok(Capability::CAP_NET_ADMIN),
152 "NET_RAW" => Ok(Capability::CAP_NET_RAW),
153 "IPC_LOCK" => Ok(Capability::CAP_IPC_LOCK),
154 "IPC_OWNER" => Ok(Capability::CAP_IPC_OWNER),
155 "SYS_MODULE" => Ok(Capability::CAP_SYS_MODULE),
156 "SYS_RAWIO" => Ok(Capability::CAP_SYS_RAWIO),
157 "SYS_CHROOT" => Ok(Capability::CAP_SYS_CHROOT),
158 "SYS_PTRACE" => Ok(Capability::CAP_SYS_PTRACE),
159 "SYS_PACCT" => Ok(Capability::CAP_SYS_PACCT),
160 "SYS_ADMIN" => Ok(Capability::CAP_SYS_ADMIN),
161 "SYS_BOOT" => Ok(Capability::CAP_SYS_BOOT),
162 "SYS_NICE" => Ok(Capability::CAP_SYS_NICE),
163 "SYS_RESOURCE" => Ok(Capability::CAP_SYS_RESOURCE),
164 "SYS_TIME" => Ok(Capability::CAP_SYS_TIME),
165 "SYS_TTY_CONFIG" => Ok(Capability::CAP_SYS_TTY_CONFIG),
166 "MKNOD" => Ok(Capability::CAP_MKNOD),
167 "LEASE" => Ok(Capability::CAP_LEASE),
168 "AUDIT_WRITE" => Ok(Capability::CAP_AUDIT_WRITE),
169 "AUDIT_CONTROL" => Ok(Capability::CAP_AUDIT_CONTROL),
170 "SETFCAP" => Ok(Capability::CAP_SETFCAP),
171 "MAC_OVERRIDE" => Ok(Capability::CAP_MAC_OVERRIDE),
172 "MAC_ADMIN" => Ok(Capability::CAP_MAC_ADMIN),
173 "SYSLOG" => Ok(Capability::CAP_SYSLOG),
174 "WAKE_ALARM" => Ok(Capability::CAP_WAKE_ALARM),
175 "BLOCK_SUSPEND" => Ok(Capability::CAP_BLOCK_SUSPEND),
176 "AUDIT_READ" => Ok(Capability::CAP_AUDIT_READ),
177 "PERFMON" => Ok(Capability::CAP_PERFMON),
178 "BPF" => Ok(Capability::CAP_BPF),
179 "CHECKPOINT_RESTORE" => Ok(Capability::CAP_CHECKPOINT_RESTORE),
180 _ => Err(NucleusError::ConfigError(format!(
181 "Unknown capability: '{}'. Use Linux names like NET_BIND_SERVICE.",
182 name
183 ))),
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190
191 #[test]
192 fn test_parse_drop_all_policy() {
193 let toml = r#"
194[bounding]
195keep = []
196
197[ambient]
198keep = []
199"#;
200 let policy: CapsPolicy = toml::from_str(toml).unwrap();
201 assert!(policy.bounding.keep.is_empty());
202 assert!(policy.resolve_keep_set().unwrap().is_empty());
203 }
204
205 #[test]
206 fn test_parse_keep_some_policy() {
207 let toml = r#"
208[bounding]
209keep = ["NET_BIND_SERVICE", "CHOWN"]
210"#;
211 let policy: CapsPolicy = toml::from_str(toml).unwrap();
212 let keep = policy.resolve_keep_set().unwrap();
213 assert_eq!(keep.len(), 2);
214 assert!(keep.contains(&Capability::CAP_NET_BIND_SERVICE));
215 assert!(keep.contains(&Capability::CAP_CHOWN));
216 }
217
218 #[test]
219 fn test_parse_cap_prefix() {
220 assert_eq!(
221 parse_capability_name("CAP_NET_RAW").unwrap(),
222 Capability::CAP_NET_RAW
223 );
224 assert_eq!(
225 parse_capability_name("NET_RAW").unwrap(),
226 Capability::CAP_NET_RAW
227 );
228 }
229
230 #[test]
231 fn test_unknown_capability_error() {
232 assert!(parse_capability_name("DOES_NOT_EXIST").is_err());
233 }
234
235 #[test]
236 fn test_default_policy_is_drop_all() {
237 let toml = "";
238 let policy: CapsPolicy = toml::from_str(toml).unwrap();
239 assert!(policy.resolve_keep_set().unwrap().is_empty());
240 }
241
242 #[test]
243 fn test_dedup_across_sets() {
244 let toml = r#"
245[bounding]
246keep = ["CHOWN"]
247
248[effective]
249keep = ["CHOWN"]
250"#;
251 let policy: CapsPolicy = toml::from_str(toml).unwrap();
252 let keep = policy.resolve_keep_set().unwrap();
253 assert_eq!(keep.len(), 1);
254 }
255
256 #[test]
257 fn test_resolve_sets_preserves_set_specificity() {
258 let toml = r#"
259[bounding]
260keep = ["NET_BIND_SERVICE"]
261
262[effective]
263keep = ["CHOWN"]
264
265[ambient]
266keep = ["NET_BIND_SERVICE"]
267"#;
268 let policy: CapsPolicy = toml::from_str(toml).unwrap();
269 let resolved = policy.resolve_sets().unwrap();
270
271 assert_eq!(resolved.bounding, vec![Capability::CAP_NET_BIND_SERVICE]);
272 assert_eq!(resolved.effective, vec![Capability::CAP_CHOWN]);
273 assert_eq!(resolved.ambient, vec![Capability::CAP_NET_BIND_SERVICE]);
274 assert_eq!(resolved.inheritable, vec![Capability::CAP_NET_BIND_SERVICE]);
275 assert_eq!(
276 resolved.permitted,
277 vec![Capability::CAP_CHOWN, Capability::CAP_NET_BIND_SERVICE]
278 );
279 }
280
281 #[test]
282 fn test_ambient_caps_promote_into_inheritable_and_permitted() {
283 let toml = r#"
284[ambient]
285keep = ["NET_RAW"]
286"#;
287 let policy: CapsPolicy = toml::from_str(toml).unwrap();
288 let resolved = policy.resolve_sets().unwrap();
289
290 assert_eq!(resolved.ambient, vec![Capability::CAP_NET_RAW]);
291 assert_eq!(resolved.inheritable, vec![Capability::CAP_NET_RAW]);
292 assert_eq!(resolved.permitted, vec![Capability::CAP_NET_RAW]);
293 }
294}