1use bon::{bon, builder, Builder};
2use capctl::{Cap, CapSet};
3use derivative::Derivative;
4use serde::{
5 de::{self, MapAccess, SeqAccess, Visitor},
6 ser::SerializeMap,
7 Deserialize, Deserializer, Serialize,
8};
9use serde_json::{Map, Value};
10use strum::{Display, EnumIs};
11
12use std::{
13 cell::RefCell,
14 error::Error,
15 fmt,
16 ops::{Index, Not},
17 rc::{Rc, Weak},
18};
19
20use super::{
21 actor::{SActor, SGroups, SUserType},
22 is_default,
23 options::{Level, Opt, OptBuilder},
24};
25
26#[derive(Deserialize, Serialize, PartialEq, Eq, Debug)]
27pub struct SConfig {
28 #[serde(
29 default,
30 skip_serializing_if = "Option::is_none",
31 deserialize_with = "sconfig_opt"
32 )]
33 pub options: Option<Rc<RefCell<Opt>>>,
34 #[serde(default, skip_serializing_if = "Vec::is_empty")]
35 pub roles: Vec<Rc<RefCell<SRole>>>,
36 #[serde(default)]
37 #[serde(flatten, skip_serializing_if = "Map::is_empty")]
38 pub _extra_fields: Map<String, Value>,
39}
40
41fn sconfig_opt<'de, D>(deserializer: D) -> Result<Option<Rc<RefCell<Opt>>>, D::Error>
42where
43 D: Deserializer<'de>,
44{
45 let mut opt = Opt::deserialize(deserializer)?;
46 opt.level = Level::Global;
47 Ok(Some(Rc::new(RefCell::new(opt))))
48}
49
50#[derive(Serialize, Deserialize, Debug, Derivative)]
51#[serde(rename_all = "kebab-case")]
52#[derivative(PartialEq, Eq)]
53pub struct SRole {
54 pub name: String,
55 #[serde(default, skip_serializing_if = "Vec::is_empty")]
56 pub actors: Vec<SActor>,
57 #[serde(default, skip_serializing_if = "Vec::is_empty")]
58 pub tasks: Vec<Rc<RefCell<STask>>>,
59 #[serde(
60 default,
61 skip_serializing_if = "Option::is_none",
62 deserialize_with = "srole_opt"
63 )]
64 pub options: Option<Rc<RefCell<Opt>>>,
65 #[serde(default, flatten, skip_serializing_if = "Map::is_empty")]
66 pub _extra_fields: Map<String, Value>,
67 #[serde(skip)]
68 #[derivative(PartialEq = "ignore")]
69 pub _config: Option<Weak<RefCell<SConfig>>>,
70}
71
72fn srole_opt<'de, D>(deserializer: D) -> Result<Option<Rc<RefCell<Opt>>>, D::Error>
73where
74 D: Deserializer<'de>,
75{
76 let mut opt = Opt::deserialize(deserializer)?;
77 opt.level = Level::Role;
78 Ok(Some(Rc::new(RefCell::new(opt))))
79}
80
81#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs)]
82#[serde(untagged)]
83pub enum IdTask {
84 Name(String),
85 Number(usize),
86}
87
88impl std::fmt::Display for IdTask {
89 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
90 match self {
91 IdTask::Name(name) => write!(f, "{}", name),
92 IdTask::Number(id) => write!(f, "{}", id),
93 }
94 }
95}
96
97#[derive(Serialize, Deserialize, Debug, Derivative)]
98#[derivative(PartialEq, Eq)]
99pub struct STask {
100 #[serde(default, skip_serializing_if = "IdTask::is_number")]
101 pub name: IdTask,
102 #[serde(skip_serializing_if = "Option::is_none")]
103 pub purpose: Option<String>,
104 #[serde(default, skip_serializing_if = "is_default")]
105 pub cred: SCredentials,
106 #[serde(default, skip_serializing_if = "is_default")]
107 pub commands: SCommands,
108 #[serde(
109 default,
110 skip_serializing_if = "Option::is_none",
111 deserialize_with = "stask_opt"
112 )]
113 pub options: Option<Rc<RefCell<Opt>>>,
114 #[serde(default, flatten, skip_serializing_if = "Map::is_empty")]
115 pub _extra_fields: Map<String, Value>,
116 #[serde(skip)]
117 #[derivative(PartialEq = "ignore")]
118 pub _role: Option<Weak<RefCell<SRole>>>,
119}
120
121fn stask_opt<'de, D>(deserializer: D) -> Result<Option<Rc<RefCell<Opt>>>, D::Error>
122where
123 D: Deserializer<'de>,
124{
125 let mut opt = Opt::deserialize(deserializer)?;
126 opt.level = Level::Task;
127 Ok(Some(Rc::new(RefCell::new(opt))))
128}
129
130#[derive(Serialize, Deserialize, Debug, Builder, PartialEq, Eq)]
131#[serde(rename_all = "kebab-case")]
132pub struct SCredentials {
133 #[serde(skip_serializing_if = "Option::is_none")]
134 #[builder(into)]
135 pub setuid: Option<SUserChooser>,
136 #[serde(skip_serializing_if = "Option::is_none")]
137 #[builder(into)]
138 pub setgid: Option<SGroups>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
140 pub capabilities: Option<SCapabilities>,
141 #[serde(default, skip_serializing_if = "Option::is_none")]
142 #[builder(into)]
143 pub additional_auth: Option<String>, #[serde(default, flatten, skip_serializing_if = "Map::is_empty")]
145 #[builder(default)]
146 pub _extra_fields: Map<String, Value>,
147}
148
149#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
150#[serde(untagged)]
151pub enum SUserChooser {
152 Actor(SUserType),
153 ChooserStruct(SSetuidSet),
154}
155
156impl From<SUserType> for SUserChooser {
157 fn from(actor: SUserType) -> Self {
158 SUserChooser::Actor(actor)
159 }
160}
161
162impl From<SSetuidSet> for SUserChooser {
163 fn from(set: SSetuidSet) -> Self {
164 SUserChooser::ChooserStruct(set)
165 }
166}
167
168impl From<&str> for SUserChooser {
169 fn from(name: &str) -> Self {
170 SUserChooser::Actor(name.into())
171 }
172}
173
174impl From<u32> for SUserChooser {
175 fn from(id: u32) -> Self {
176 SUserChooser::Actor(id.into())
177 }
178}
179
180#[derive(Serialize, Deserialize, Debug, Clone, Builder, PartialEq, Eq)]
181
182pub struct SSetuidSet {
183 #[builder(start_fn, into)]
184 pub fallback: SUserType,
185 #[serde(rename = "default", default, skip_serializing_if = "is_default")]
186 #[builder(start_fn)]
187 pub default: SetBehavior,
188 #[serde(default, skip_serializing_if = "Vec::is_empty")]
189 #[builder(default, with = FromIterator::from_iter)]
190 pub add: Vec<SUserType>,
191 #[serde(default, skip_serializing_if = "Vec::is_empty")]
192 #[builder(default, with = FromIterator::from_iter)]
193 pub sub: Vec<SUserType>,
194}
195
196#[derive(Serialize, Deserialize, PartialEq, Eq, Display, Debug, EnumIs, Clone)]
197#[serde(rename_all = "lowercase")]
198#[derive(Default)]
199pub enum SetBehavior {
200 All,
201 #[default]
202 None,
203}
204
205#[derive(PartialEq, Eq, Debug, Builder)]
206pub struct SCapabilities {
207 #[builder(start_fn)]
208 pub default_behavior: SetBehavior,
209 #[builder(field)]
210 pub add: CapSet,
211 #[builder(field)]
212 pub sub: CapSet,
213 #[builder(default, with = <_>::from_iter)]
214 pub _extra_fields: Map<String, Value>,
215}
216
217impl<S: s_capabilities_builder::State> SCapabilitiesBuilder<S> {
218 pub fn add_cap(mut self, cap: Cap) -> Self {
219 self.add.add(cap);
220 self
221 }
222 pub fn add_all(mut self, set: CapSet) -> Self {
223 self.add = set;
224 self
225 }
226 pub fn sub_cap(mut self, cap: Cap) -> Self {
227 self.sub.add(cap);
228 self
229 }
230 pub fn sub_all(mut self, set: CapSet) -> Self {
231 self.sub = set;
232 self
233 }
234}
235
236impl Serialize for SCapabilities {
237 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
238 where
239 S: serde::Serializer,
240 {
241 if self.default_behavior.is_none() && self.sub.is_empty() && self._extra_fields.is_empty() {
242 super::serialize_capset(&self.add, serializer)
243 } else {
244 let mut map = serializer.serialize_map(Some(3))?;
245 if self.default_behavior.is_none() {
246 map.serialize_entry("default", &self.default_behavior)?;
247 }
248 if !self.add.is_empty() {
249 let v: Vec<String> = self.add.iter().map(|cap| cap.to_string()).collect();
250 map.serialize_entry("add", &v)?;
251 }
252 if !self.sub.is_empty() {
253 let v: Vec<String> = self.sub.iter().map(|cap| cap.to_string()).collect();
254 map.serialize_entry("del", &v)?;
255 }
256 for (key, value) in &self._extra_fields {
257 map.serialize_entry(key, value)?;
258 }
259 map.end()
260 }
261 }
262}
263impl<'de> Deserialize<'de> for SCapabilities {
264 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
265 where
266 D: Deserializer<'de>,
267 {
268 struct SCapabilitiesVisitor;
269
270 impl<'de> Visitor<'de> for SCapabilitiesVisitor {
271 type Value = SCapabilities;
272
273 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
274 formatter.write_str("an array of strings or a map with SCapabilities fields")
275 }
276
277 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
278 where
279 A: SeqAccess<'de>,
280 {
281 let mut add = CapSet::default();
282 while let Some(cap) = seq.next_element::<String>()? {
283 add.add(cap.parse().map_err(de::Error::custom)?);
284 }
285
286 Ok(SCapabilities {
287 default_behavior: SetBehavior::None,
288 add,
289 sub: CapSet::default(),
290 _extra_fields: Map::new(),
291 })
292 }
293
294 fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
295 where
296 A: MapAccess<'de>,
297 {
298 let mut default_behavior = SetBehavior::None;
299 let mut add = CapSet::default();
300 let mut sub = CapSet::default();
301 let mut _extra_fields = Map::new();
302
303 while let Some(key) = map.next_key::<String>()? {
304 match key.as_str() {
305 "default" => {
306 default_behavior = map
307 .next_value()
308 .expect("default entry must be either 'all' or 'none'");
309 }
310 "add" => {
311 let values: Vec<String> =
312 map.next_value().expect("add entry must be a list");
313 for value in values {
314 add.add(value.parse().map_err(|_| {
315 de::Error::custom(format!("Invalid capability: {}", value))
316 })?);
317 }
318 }
319 "sub" | "del" => {
320 let values: Vec<String> =
321 map.next_value().expect("sub entry must be a list");
322 for value in values {
323 sub.add(value.parse().map_err(|_| {
324 de::Error::custom(format!("Invalid capability: {}", value))
325 })?);
326 }
327 }
328 other => {
329 _extra_fields.insert(other.to_string(), map.next_value()?);
330 }
331 }
332 }
333
334 Ok(SCapabilities {
335 default_behavior,
336 add,
337 sub,
338 _extra_fields,
339 })
340 }
341 }
342
343 deserializer.deserialize_any(SCapabilitiesVisitor)
344 }
345}
346
347#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs, Clone)]
348#[serde(untagged)]
349pub enum SCommand {
350 Simple(String),
351 Complex(Value),
352}
353
354#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)]
355pub struct SCommands {
356 #[serde(rename = "default")]
357 pub default_behavior: Option<SetBehavior>,
358 #[serde(default, skip_serializing_if = "Vec::is_empty")]
359 pub add: Vec<SCommand>,
360 #[serde(default, alias = "del", skip_serializing_if = "Vec::is_empty")]
361 pub sub: Vec<SCommand>,
362 #[serde(default, flatten, skip_serializing_if = "Map::is_empty")]
363 pub _extra_fields: Map<String, Value>,
364}
365
366impl Default for SConfig {
371 fn default() -> Self {
372 SConfig {
373 options: Some(Rc::new(RefCell::new(Opt::default()))),
374 roles: Vec::new(),
375 _extra_fields: Map::default(),
376 }
377 }
378}
379
380impl Default for SRole {
381 fn default() -> Self {
382 SRole {
383 name: "".to_string(),
384 actors: Vec::new(),
385 tasks: Vec::new(),
386 options: None,
387 _extra_fields: Map::default(),
388 _config: None,
389 }
390 }
391}
392
393impl Default for STask {
394 fn default() -> Self {
395 STask {
396 name: IdTask::Number(0),
397 purpose: None,
398 cred: SCredentials::default(),
399 commands: SCommands::default(),
400 options: None,
401 _extra_fields: Map::default(),
402 _role: None,
403 }
404 }
405}
406
407impl Default for SCredentials {
408 fn default() -> Self {
409 SCredentials {
410 setuid: None,
411 setgid: None,
412 capabilities: Some(SCapabilities::default()),
413 additional_auth: None,
414 _extra_fields: Map::default(),
415 }
416 }
417}
418
419impl Default for SCommands {
420 fn default() -> Self {
421 SCommands {
422 default_behavior: Some(SetBehavior::default()),
423 add: Vec::new(),
424 sub: Vec::new(),
425 _extra_fields: Map::default(),
426 }
427 }
428}
429
430impl Default for SCapabilities {
431 fn default() -> Self {
432 SCapabilities {
433 default_behavior: SetBehavior::default(),
434 add: CapSet::empty(),
435 sub: CapSet::empty(),
436 _extra_fields: Map::default(),
437 }
438 }
439}
440
441impl Default for SSetuidSet {
442 fn default() -> Self {
443 SSetuidSet::builder(0, SetBehavior::None).build()
444 }
445}
446
447impl Default for IdTask {
448 fn default() -> Self {
449 IdTask::Number(0)
450 }
451}
452
453impl From<usize> for IdTask {
458 fn from(id: usize) -> Self {
459 IdTask::Number(id)
460 }
461}
462
463impl From<String> for IdTask {
464 fn from(name: String) -> Self {
465 IdTask::Name(name)
466 }
467}
468
469impl From<&str> for IdTask {
470 fn from(name: &str) -> Self {
471 IdTask::Name(name.to_string())
472 }
473}
474
475impl From<&str> for SCommand {
476 fn from(name: &str) -> Self {
477 SCommand::Simple(name.to_string())
478 }
479}
480
481impl From<CapSet> for SCapabilities {
482 fn from(capset: CapSet) -> Self {
483 SCapabilities {
484 add: capset,
485 ..Default::default()
486 }
487 }
488}
489
490#[bon]
500impl SConfig {
501 #[builder]
502 pub fn new(
503 #[builder(field)] roles: Vec<Rc<RefCell<SRole>>>,
504 #[builder(with = |f : fn(OptBuilder) -> Rc<RefCell<Opt>> | f(Opt::builder(Level::Global)))]
505 options: Option<Rc<RefCell<Opt>>>,
506 _extra_fields: Option<Map<String, Value>>,
507 ) -> Rc<RefCell<Self>> {
508 let c = Rc::new(RefCell::new(SConfig {
509 roles: roles.clone(),
510 options: options.clone(),
511 _extra_fields: _extra_fields.unwrap_or_default().clone(),
512 }));
513 for role in &roles {
514 role.borrow_mut()._config = Some(Rc::downgrade(&c));
515 }
516 c
517 }
518}
519
520pub trait RoleGetter {
521 fn role(&self, name: &str) -> Option<Rc<RefCell<SRole>>>;
522 fn task<T: Into<IdTask>>(
523 &self,
524 role: &str,
525 name: T,
526 ) -> Result<Rc<RefCell<STask>>, Box<dyn Error>>;
527}
528
529pub trait TaskGetter {
530 fn task(&self, name: &IdTask) -> Option<Rc<RefCell<STask>>>;
531}
532
533impl RoleGetter for Rc<RefCell<SConfig>> {
534 fn role(&self, name: &str) -> Option<Rc<RefCell<SRole>>> {
535 self.as_ref()
536 .borrow()
537 .roles
538 .iter()
539 .find(|role| role.borrow().name == name)
540 .cloned()
541 }
542 fn task<T: Into<IdTask>>(
543 &self,
544 role: &str,
545 name: T,
546 ) -> Result<Rc<RefCell<STask>>, Box<dyn Error>> {
547 let name = name.into();
548 self.role(role)
549 .and_then(|role| role.as_ref().borrow().task(&name).cloned())
550 .ok_or_else(|| format!("Task {} not found in role {}", name, role).into())
551 }
552}
553
554impl TaskGetter for Rc<RefCell<SRole>> {
555 fn task(&self, name: &IdTask) -> Option<Rc<RefCell<STask>>> {
556 self.as_ref()
557 .borrow()
558 .tasks
559 .iter()
560 .find(|task| task.borrow().name == *name)
561 .cloned()
562 }
563}
564
565impl<S: s_config_builder::State> SConfigBuilder<S> {
566 pub fn role(mut self, role: Rc<RefCell<SRole>>) -> Self {
567 self.roles.push(role);
568 self
569 }
570 pub fn roles(mut self, roles: impl IntoIterator<Item = Rc<RefCell<SRole>>>) -> Self {
571 self.roles.extend(roles);
572 self
573 }
574}
575
576impl<S: s_role_builder::State> SRoleBuilder<S> {
577 pub fn task(mut self, task: Rc<RefCell<STask>>) -> Self {
578 self.tasks.push(task);
579 self
580 }
581 pub fn actor(mut self, actor: SActor) -> Self {
582 self.actors.push(actor);
583 self
584 }
585}
586
587#[bon]
588impl SRole {
589 #[builder]
590 pub fn new(
591 #[builder(start_fn, into)] name: String,
592 #[builder(field)] tasks: Vec<Rc<RefCell<STask>>>,
593 #[builder(field)] actors: Vec<SActor>,
594 #[builder(with = |f : fn(OptBuilder) -> Rc<RefCell<Opt>> | f(Opt::builder(Level::Role)))]
595 options: Option<Rc<RefCell<Opt>>>,
596 #[builder(default)] _extra_fields: Map<String, Value>,
597 ) -> Rc<RefCell<Self>> {
598 let s = Rc::new(RefCell::new(SRole {
599 name,
600 actors,
601 tasks,
602 options,
603 _extra_fields,
604 _config: None,
605 }));
606 for task in s.as_ref().borrow_mut().tasks.iter() {
607 task.borrow_mut()._role = Some(Rc::downgrade(&s));
608 }
609 s
610 }
611 pub fn config(&self) -> Option<Rc<RefCell<SConfig>>> {
612 self._config.as_ref()?.upgrade()
613 }
614 pub fn task(&self, name: &IdTask) -> Option<&Rc<RefCell<STask>>> {
615 self.tasks
616 .iter()
617 .find(|task| task.as_ref().borrow().name == *name)
618 }
619}
620
621#[bon]
622impl STask {
623 #[builder]
624 pub fn new(
625 #[builder(start_fn, into)] name: IdTask,
626 purpose: Option<String>,
627 #[builder(default)] cred: SCredentials,
628 #[builder(default)] commands: SCommands,
629 #[builder(with = |f : fn(OptBuilder) -> Rc<RefCell<Opt>> | f(Opt::builder(Level::Task)))]
630 options: Option<Rc<RefCell<Opt>>>,
631 #[builder(default)] _extra_fields: Map<String, Value>,
632 _role: Option<Weak<RefCell<SRole>>>,
633 ) -> Rc<RefCell<Self>> {
634 Rc::new(RefCell::new(STask {
635 name,
636 purpose,
637 cred,
638 commands,
639 options,
640 _extra_fields,
641 _role,
642 }))
643 }
644 pub fn role(&self) -> Option<Rc<RefCell<SRole>>> {
645 self._role.as_ref()?.upgrade()
646 }
647}
648
649impl Index<usize> for SConfig {
650 type Output = Rc<RefCell<SRole>>;
651
652 fn index(&self, index: usize) -> &Self::Output {
653 &self.roles[index]
654 }
655}
656
657impl Index<usize> for SRole {
658 type Output = Rc<RefCell<STask>>;
659
660 fn index(&self, index: usize) -> &Self::Output {
661 &self.tasks[index]
662 }
663}
664
665#[bon]
666impl SCommands {
667 #[builder]
668 pub fn new(
669 #[builder(start_fn)] default_behavior: SetBehavior,
670 #[builder(default, with = FromIterator::from_iter)] add: Vec<SCommand>,
671 #[builder(default, with = FromIterator::from_iter)] sub: Vec<SCommand>,
672 #[builder(default, with = <_>::from_iter)] _extra_fields: Map<String, Value>,
673 ) -> Self {
674 SCommands {
675 default_behavior: Some(default_behavior),
676 add,
677 sub,
678 _extra_fields,
679 }
680 }
681}
682
683impl SCapabilities {
684 pub fn to_capset(&self) -> CapSet {
685 let mut capset = match self.default_behavior {
686 SetBehavior::All => capctl::bounding::probe() & CapSet::not(CapSet::empty()),
687 SetBehavior::None => CapSet::empty(),
688 };
689 capset = capset.union(self.add);
690 capset.drop_all(self.sub);
691 capset
692 }
693}
694
695impl PartialEq<str> for SUserChooser {
696 fn eq(&self, other: &str) -> bool {
697 match self {
698 SUserChooser::Actor(actor) => actor == &SUserType::from(other),
699 SUserChooser::ChooserStruct(chooser) => chooser.fallback == *other,
700 }
701 }
702}
703
704#[cfg(test)]
705mod tests {
706
707 use capctl::Cap;
708 use chrono::Duration;
709 use linked_hash_set::LinkedHashSet;
710
711 use crate::{
712 as_borrow,
713 database::{
714 actor::SGroupType,
715 options::{
716 EnvBehavior, PathBehavior, SAuthentication, SBounding, SEnvOptions, SPathOptions,
717 SPrivileged, STimeout, TimestampType,
718 },
719 },
720 };
721
722 use super::*;
723
724 #[test]
725 fn test_deserialize() {
726 println!("START");
727 let config = r#"
728 {
729 "options": {
730 "path": {
731 "default": "delete",
732 "add": ["path_add"],
733 "sub": ["path_sub"]
734 },
735 "env": {
736 "default": "delete",
737 "override_behavior": true,
738 "keep": ["keep_env"],
739 "check": ["check_env"]
740 },
741 "root": "privileged",
742 "bounding": "ignore",
743 "authentication": "skip",
744 "wildcard-denied": "wildcards",
745 "timeout": {
746 "type": "ppid",
747 "duration": "00:05:00"
748 }
749 },
750 "roles": [
751 {
752 "name": "role1",
753 "actors": [
754 {
755 "type": "user",
756 "name": "user1"
757 },
758 {
759 "type":"group",
760 "groups": ["group1","1000"]
761 }
762 ],
763 "tasks": [
764 {
765 "name": "task1",
766 "purpose": "purpose1",
767 "cred": {
768 "setuid": {
769 "fallback": "user1",
770 "default": "all",
771 "add": ["user2"],
772 "sub": ["user3"]
773 },
774 "setgid": "setgid1",
775 "capabilities": {
776 "default": "all",
777 "add": ["cap_net_bind_service"],
778 "sub": ["cap_sys_admin"]
779 }
780 },
781 "commands": {
782 "default": "all",
783 "add": ["cmd1"],
784 "sub": ["cmd2"]
785 }
786 }
787 ]
788 }
789 ]
790 }
791 "#;
792 println!("STEP 1");
793 let config: SConfig = serde_json::from_str(config).unwrap();
794 let options = config.options.as_ref().unwrap().as_ref().borrow();
795 let path = options.path.as_ref().unwrap();
796 assert_eq!(path.default_behavior, PathBehavior::Delete);
797 let default = LinkedHashSet::new();
798 assert!(path
799 .add
800 .as_ref()
801 .unwrap_or(&default)
802 .front()
803 .is_some_and(|s| s == "path_add"));
804 let env = options.env.as_ref().unwrap();
805 assert_eq!(env.default_behavior, EnvBehavior::Delete);
806 assert!(env.override_behavior.is_some_and(|b| b));
807 assert!(env
808 .keep
809 .as_ref()
810 .unwrap_or(&LinkedHashSet::new())
811 .front()
812 .is_some_and(|s| s == "keep_env"));
813 assert!(env
814 .check
815 .as_ref()
816 .unwrap_or(&LinkedHashSet::new())
817 .front()
818 .is_some_and(|s| s == "check_env"));
819 assert!(options.root.as_ref().unwrap().is_privileged());
820 assert!(options.bounding.as_ref().unwrap().is_ignore());
821 assert_eq!(options.authentication, Some(SAuthentication::Skip));
822 assert_eq!(options.wildcard_denied.as_ref().unwrap(), "wildcards");
823
824 let timeout = options.timeout.as_ref().unwrap();
825 assert_eq!(timeout.type_field, Some(TimestampType::PPID));
826 assert_eq!(timeout.duration, Some(Duration::minutes(5)));
827 assert_eq!(config.roles[0].as_ref().borrow().name, "role1");
828 let actor0 = &config.roles[0].as_ref().borrow().actors[0];
829 assert_eq!(
830 actor0,
831 &SActor::User {
832 id: Some("user1".into()),
833 _extra_fields: Map::default()
834 }
835 );
836 let actor1 = &config.roles[0].as_ref().borrow().actors[1];
837 match actor1 {
838 SActor::Group { groups, .. } => match groups.as_ref().unwrap() {
839 SGroups::Multiple(groups) => {
840 assert_eq!(&groups[0], "group1");
841 assert_eq!(groups[1], 1000);
842 }
843 _ => panic!("unexpected actor group type"),
844 },
845 _ => panic!("unexpected actor {:?}", actor1),
846 }
847 let role = config.roles[0].as_ref().borrow();
848 assert_eq!(as_borrow!(role[0]).purpose.as_ref().unwrap(), "purpose1");
849 let cred = &as_borrow!(&role[0]).cred;
850 let setuidstruct = SSetuidSet {
851 fallback: "user1".into(),
852 default: SetBehavior::All,
853 add: ["user2".into()].into(),
854 sub: ["user3".into()].into(),
855 };
856 assert!(
857 matches!(cred.setuid.as_ref().unwrap(), SUserChooser::ChooserStruct(set) if set == &setuidstruct)
858 );
859 assert_eq!(*cred.setgid.as_ref().unwrap(), ["setgid1".into()]);
860 let capabilities = cred.capabilities.as_ref().unwrap();
861 assert_eq!(capabilities.default_behavior, SetBehavior::All);
862 assert!(capabilities.add.has(Cap::NET_BIND_SERVICE));
863 assert!(capabilities.sub.has(Cap::SYS_ADMIN));
864 let commands = &as_borrow!(&role[0]).commands;
865 assert_eq!(
866 *commands.default_behavior.as_ref().unwrap(),
867 SetBehavior::All
868 );
869 assert_eq!(commands.add[0], SCommand::Simple("cmd1".into()));
870 assert_eq!(commands.sub[0], SCommand::Simple("cmd2".into()));
871 }
872 #[test]
873 fn test_unknown_fields() {
874 let config = r#"
875 {
876 "options": {
877 "path": {
878 "default": "delete",
879 "add": ["path_add"],
880 "sub": ["path_sub"],
881 "unknown": "unknown"
882 },
883 "env": {
884 "default": "delete",
885 "keep": ["keep_env"],
886 "check": ["check_env"],
887 "unknown": "unknown"
888 },
889 "allow-root": false,
890 "allow-bounding": false,
891 "wildcard-denied": "wildcards",
892 "timeout": {
893 "type": "ppid",
894 "duration": "00:05:00",
895 "unknown": "unknown"
896 },
897 "unknown": "unknown"
898 },
899 "roles": [
900 {
901 "name": "role1",
902 "actors": [
903 {
904 "type": "user",
905 "name": "user1",
906 "unknown": "unknown"
907 },
908 {
909 "type":"bla",
910 "unknown": "unknown"
911 }
912 ],
913 "tasks": [
914 {
915 "name": "task1",
916 "purpose": "purpose1",
917 "cred": {
918 "setuid": "setuid1",
919 "setgid": "setgid1",
920 "capabilities": {
921 "default": "all",
922 "add": ["cap_dac_override"],
923 "sub": ["cap_dac_override"],
924 "unknown": "unknown"
925 },
926 "unknown": "unknown"
927 },
928 "commands": {
929 "default": "all",
930 "add": ["cmd1"],
931 "sub": ["cmd2"],
932 "unknown": "unknown"
933 },
934 "unknown": "unknown"
935 }
936 ],
937 "unknown": "unknown"
938 }
939 ],
940 "unknown": "unknown"
941 }
942 "#;
943 let config: SConfig = serde_json::from_str(config).unwrap();
944 assert_eq!(config._extra_fields.get("unknown").unwrap(), "unknown");
945
946 let binding = config.options.unwrap();
947 let options = binding.as_ref().borrow();
948 let path = options.path.as_ref().unwrap();
949 assert_eq!(path._extra_fields.get("unknown").unwrap(), "unknown");
950 let env = &options.env.as_ref().unwrap();
951 assert_eq!(env._extra_fields.get("unknown").unwrap(), "unknown");
952 assert_eq!(options._extra_fields.get("unknown").unwrap(), "unknown");
953 let timeout = options.timeout.as_ref().unwrap();
954 assert_eq!(timeout._extra_fields.get("unknown").unwrap(), "unknown");
955 assert_eq!(config._extra_fields.get("unknown").unwrap(), "unknown");
956 let actor0 = &as_borrow!(config.roles[0]).actors[0];
957 match actor0 {
958 SActor::User { id, _extra_fields } => {
959 assert_eq!(id.as_ref().unwrap(), "user1");
960 assert_eq!(_extra_fields.get("unknown").unwrap(), "unknown");
961 }
962 _ => panic!("unexpected actor type"),
963 }
964 let actor1 = &as_borrow!(config.roles[0]).actors[1];
965 match actor1 {
966 SActor::Unknown(unknown) => {
967 let obj = unknown.as_object().unwrap();
968 assert_eq!(obj.get("type").unwrap().as_str().unwrap(), "bla");
969 assert_eq!(obj.get("unknown").unwrap().as_str().unwrap(), "unknown");
970 }
971 _ => panic!("unexpected actor type"),
972 }
973 assert_eq!(
974 config.roles[0].as_ref().borrow()[0]
975 .as_ref()
976 .borrow()
977 ._extra_fields
978 .get("unknown")
979 .as_ref()
980 .unwrap()
981 .as_str()
982 .unwrap(),
983 "unknown"
984 );
985 let role = config.roles[0].as_ref().borrow();
986 let cred = &role[0].as_ref().borrow().cred;
987 assert_eq!(cred._extra_fields.get("unknown").unwrap(), "unknown");
988 let capabilities = cred.capabilities.as_ref().unwrap();
989 assert_eq!(
990 capabilities._extra_fields.get("unknown").unwrap(),
991 "unknown"
992 );
993 let commands = &as_borrow!(role[0]).commands;
994 assert_eq!(commands._extra_fields.get("unknown").unwrap(), "unknown");
995 }
996
997 #[test]
998 fn test_deserialize_alias() {
999 let config = r#"
1000 {
1001 "options": {
1002 "path": {
1003 "default": "delete",
1004 "add": ["path_add"],
1005 "del": ["path_sub"]
1006 },
1007 "env": {
1008 "default": "delete",
1009 "keep": ["keep_env"],
1010 "check": ["check_env"]
1011 },
1012 "root": "privileged",
1013 "bounding": "ignore",
1014 "authentication": "skip",
1015 "wildcard-denied": "wildcards",
1016 "timeout": {
1017 "type": "ppid",
1018 "duration": "00:05:00"
1019 }
1020 },
1021 "roles": [
1022 {
1023 "name": "role1",
1024 "actors": [
1025 {
1026 "type": "user",
1027 "name": "user1"
1028 },
1029 {
1030 "type":"group",
1031 "groups": ["group1","1000"]
1032 }
1033 ],
1034 "tasks": [
1035 {
1036 "name": "task1",
1037 "purpose": "purpose1",
1038 "cred": {
1039 "setuid": "setuid1",
1040 "setgid": "setgid1",
1041 "capabilities": ["cap_net_bind_service"]
1042 },
1043 "commands": {
1044 "default": "all",
1045 "add": ["cmd1"],
1046 "del": ["cmd2"]
1047 }
1048 }
1049 ]
1050 }
1051 ]
1052 }
1053 "#;
1054 let config: SConfig = serde_json::from_str(config).unwrap();
1055 let options = config.options.as_ref().unwrap().as_ref().borrow();
1056 let path = options.path.as_ref().unwrap();
1057 assert_eq!(path.default_behavior, PathBehavior::Delete);
1058 let default = LinkedHashSet::new();
1059 assert!(path
1060 .add
1061 .as_ref()
1062 .unwrap_or(&default)
1063 .front()
1064 .is_some_and(|s| s == "path_add"));
1065 let env = options.env.as_ref().unwrap();
1066 assert_eq!(env.default_behavior, EnvBehavior::Delete);
1067 assert!(env
1068 .keep
1069 .as_ref()
1070 .unwrap()
1071 .front()
1072 .is_some_and(|s| s == "keep_env"));
1073 assert!(env
1074 .check
1075 .as_ref()
1076 .unwrap()
1077 .front()
1078 .is_some_and(|s| s == "check_env"));
1079 assert!(options.root.as_ref().unwrap().is_privileged());
1080 assert!(options.bounding.as_ref().unwrap().is_ignore());
1081 assert_eq!(options.authentication, Some(SAuthentication::Skip));
1082 assert_eq!(options.wildcard_denied.as_ref().unwrap(), "wildcards");
1083
1084 let timeout = options.timeout.as_ref().unwrap();
1085 assert_eq!(timeout.type_field, Some(TimestampType::PPID));
1086 assert_eq!(timeout.duration, Some(Duration::minutes(5)));
1087 assert_eq!(config.roles[0].as_ref().borrow().name, "role1");
1088 let actor0 = &config.roles[0].as_ref().borrow().actors[0];
1089 match actor0 {
1090 SActor::User { id, .. } => {
1091 assert_eq!(id.as_ref().unwrap(), "user1");
1092 }
1093 _ => panic!("unexpected actor type"),
1094 }
1095 let actor1 = &config.roles[0].as_ref().borrow().actors[1];
1096 match actor1 {
1097 SActor::Group { groups, .. } => match groups.as_ref().unwrap() {
1098 SGroups::Multiple(groups) => {
1099 assert_eq!(groups[0], SGroupType::from("group1"));
1100 assert_eq!(groups[1], SGroupType::from(1000));
1101 }
1102 _ => panic!("unexpected actor group type"),
1103 },
1104 _ => panic!("unexpected actor {:?}", actor1),
1105 }
1106 let role = config.roles[0].as_ref().borrow();
1107 assert_eq!(as_borrow!(role[0]).purpose.as_ref().unwrap(), "purpose1");
1108 let cred = &as_borrow!(&role[0]).cred;
1109 assert_eq!(
1110 cred.setuid.as_ref().unwrap(),
1111 &SUserChooser::from(SUserType::from("setuid1"))
1112 );
1113 assert_eq!(cred.setgid.as_ref().unwrap(), &SGroups::from(["setgid1"]));
1114 let capabilities = cred.capabilities.as_ref().unwrap();
1115 assert_eq!(capabilities.default_behavior, SetBehavior::None);
1116 assert!(capabilities.add.has(Cap::NET_BIND_SERVICE));
1117 assert!(capabilities.sub.is_empty());
1118 let commands = &as_borrow!(&role[0]).commands;
1119 assert_eq!(
1120 *commands.default_behavior.as_ref().unwrap(),
1121 SetBehavior::All
1122 );
1123 assert_eq!(commands.add[0], SCommand::Simple("cmd1".into()));
1124 assert_eq!(commands.sub[0], SCommand::Simple("cmd2".into()));
1125 }
1126
1127 #[test]
1128 fn test_serialize() {
1129 let config = SConfig::builder()
1130 .role(
1131 SRole::builder("role1")
1132 .actor(SActor::user("user1").build())
1133 .actor(
1134 SActor::group([SGroupType::from("group1"), SGroupType::from(1000)]).build(),
1135 )
1136 .task(
1137 STask::builder("task1")
1138 .purpose("purpose1".into())
1139 .cred(
1140 SCredentials::builder()
1141 .setuid(SUserChooser::ChooserStruct(
1142 SSetuidSet::builder("user1", SetBehavior::All)
1143 .add(["user2".into()])
1144 .sub(["user3".into()])
1145 .build(),
1146 ))
1147 .setgid(["setgid1"])
1148 .capabilities(
1149 SCapabilities::builder(SetBehavior::All)
1150 .add_cap(Cap::NET_BIND_SERVICE)
1151 .sub_cap(Cap::SYS_ADMIN)
1152 .build(),
1153 )
1154 .build(),
1155 )
1156 .commands(
1157 SCommands::builder(SetBehavior::All)
1158 .add(["cmd1".into()])
1159 .sub(["cmd2".into()])
1160 .build(),
1161 )
1162 .build(),
1163 )
1164 .build(),
1165 )
1166 .options(|opt| {
1167 opt.path(
1168 SPathOptions::builder(PathBehavior::Delete)
1169 .add(["path_add"])
1170 .sub(["path_sub"])
1171 .build(),
1172 )
1173 .env(
1174 SEnvOptions::builder(EnvBehavior::Delete)
1175 .override_behavior(true)
1176 .keep(["keep_env"])
1177 .unwrap()
1178 .check(["check_env"])
1179 .unwrap()
1180 .build(),
1181 )
1182 .root(SPrivileged::Privileged)
1183 .bounding(SBounding::Ignore)
1184 .authentication(SAuthentication::Skip)
1185 .wildcard_denied("wildcards")
1186 .timeout(
1187 STimeout::builder()
1188 .type_field(TimestampType::PPID)
1189 .duration(Duration::minutes(5))
1190 .build(),
1191 )
1192 .build()
1193 })
1194 .build();
1195 let config = serde_json::to_string_pretty(&config).unwrap();
1196 println!("{}", config);
1197 }
1198
1199 #[test]
1200 fn test_serialize_operride_behavior_option() {
1201 let config = SConfig::builder()
1202 .options(|opt| {
1203 opt.env(
1204 SEnvOptions::builder(EnvBehavior::Inherit)
1205 .override_behavior(true)
1206 .build(),
1207 )
1208 .build()
1209 })
1210 .build();
1211 let config = serde_json::to_string(&config).unwrap();
1212 assert_eq!(
1213 config,
1214 "{\"options\":{\"env\":{\"override_behavior\":true}}}"
1215 );
1216 }
1217}