diff --git a/rar-common/src/api.rs b/rar-common/src/api.rs index d2c14c6e..c25a6ddd 100644 --- a/rar-common/src/api.rs +++ b/rar-common/src/api.rs @@ -9,9 +9,13 @@ use serde_json::Value; use strum::EnumIs; #[cfg(feature = "finder")] -use crate::database::finder::{Cred, ExecSettings, FilterMatcher, TaskMatch, UserMin}; +use crate::database::finder::{ActorMatchMin, Cred, ExecSettings, TaskMatch}; +use crate::database::FilterMatcher; -use crate::database::structs::{SActor, SConfig, SRole, STask}; +use crate::database::{ + actor::SActor, + structs::{SConfig, SRole, STask}, +}; use once_cell::sync::Lazy; static API: Lazy> = Lazy::new(|| Mutex::new(PluginManager::new())); @@ -52,7 +56,7 @@ pub type TaskMatcher = fn( matcher: &mut TaskMatch, ) -> PluginResultAction; #[cfg(feature = "finder")] -pub type UserMatcher = fn(role: &SRole, user: &Cred, user_struct: &Value) -> UserMin; +pub type UserMatcher = fn(role: &SRole, user: &Cred, user_struct: &Value) -> ActorMatchMin; pub type RoleInformation = fn(role: &SRole) -> Option; pub type ActorInformation = fn(actor: &SActor) -> Option; @@ -200,7 +204,7 @@ impl PluginManager { } #[cfg(feature = "finder")] - pub fn notify_user_matcher(role: &SRole, user: &Cred, user_struct: &Value) -> UserMin { + pub fn notify_user_matcher(role: &SRole, user: &Cred, user_struct: &Value) -> ActorMatchMin { let api = API.lock().unwrap(); for plugin in api.user_matcher_plugins.iter() { let res = plugin(role, user, user_struct); @@ -208,7 +212,7 @@ impl PluginManager { return res; } } - UserMin::NoMatch + ActorMatchMin::NoMatch } #[cfg(feature = "finder")] diff --git a/rar-common/src/database/actor.rs b/rar-common/src/database/actor.rs new file mode 100644 index 00000000..6c72e1d0 --- /dev/null +++ b/rar-common/src/database/actor.rs @@ -0,0 +1,420 @@ +use std::fmt::{self, Formatter}; + +use bon::bon; +use nix::unistd::{Group, User}; +use serde::{ + de::{self, Visitor}, + Deserialize, Deserializer, Serialize, +}; +use serde_json::{Map, Value}; +use strum::EnumIs; + +#[derive(Serialize, Debug, EnumIs, Clone, PartialEq, Eq)] +#[serde(untagged, rename_all = "lowercase")] +pub enum SGenericActorType { + Id(u32), + Name(String), +} + +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct SUserType(SGenericActorType); + +impl SUserType { + pub(super) fn fetch_id(&self) -> Option { + match &self.0 { + SGenericActorType::Id(id) => Some(*id), + SGenericActorType::Name(name) => match User::from_name(name) { + Ok(Some(user)) => Some(user.uid.as_raw()), + _ => None, + }, + } + } + pub fn fetch_user(&self) -> Option { + match &self.0 { + SGenericActorType::Id(id) => User::from_uid((*id).into()).ok().flatten(), + SGenericActorType::Name(name) => User::from_name(name).ok().flatten(), + } + } + pub fn fetch_eq(&self, other: &Self) -> bool { + let uid = self.fetch_id(); + let ouid = other.fetch_id(); + match (uid, ouid) { + (Some(uid), Some(ouid)) => uid == ouid, + _ => false, + } + } +} + +impl fmt::Display for SUserType { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match &self.0 { + SGenericActorType::Id(id) => write!(f, "{}", id), + SGenericActorType::Name(name) => write!(f, "{}", name), + } + } +} + +#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq)] +pub struct SGroupType(SGenericActorType); + +impl fmt::Display for SGroupType { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match &self.0 { + SGenericActorType::Id(id) => write!(f, "{}", id), + SGenericActorType::Name(name) => write!(f, "{}", name), + } + } +} + +impl SGroupType { + pub(super) fn fetch_id(&self) -> Option { + match &self.0 { + SGenericActorType::Id(id) => Some(*id), + SGenericActorType::Name(name) => match Group::from_name(name) { + Ok(Some(group)) => Some(group.gid.as_raw()), + _ => None, + }, + } + } + pub fn fetch_group(&self) -> Option { + match &self.0 { + SGenericActorType::Id(id) => Group::from_gid((*id).into()).ok().flatten(), + SGenericActorType::Name(name) => Group::from_name(name).ok().flatten(), + } + } +} + +impl std::fmt::Display for SGenericActorType { + fn fmt(&self, f: &mut Formatter) -> fmt::Result { + match self { + SGenericActorType::Id(id) => write!(f, "{}", id), + SGenericActorType::Name(name) => write!(f, "{}", name), + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, EnumIs)] +#[serde(untagged)] +pub enum SGroups { + Single(SGroupType), + Multiple(Vec), +} + +impl SGroups { + pub fn len(&self) -> usize { + match self { + SGroups::Single(_) => 1, + SGroups::Multiple(groups) => groups.len(), + } + } + pub fn is_empty(&self) -> bool { + self.len() == 0 + } +} + +impl<'de> Deserialize<'de> for SGenericActorType { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct IdVisitor; + + impl<'de> Visitor<'de> for IdVisitor { + type Value = SGenericActorType; + + fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("user ID as a number or string") + } + + fn visit_u32(self, id: u32) -> Result + where + E: de::Error, + { + Ok(SGenericActorType::Id(id)) + } + + fn visit_str(self, id: &str) -> Result + where + E: de::Error, + { + let rid: Result = id.parse(); + match rid { + Ok(id) => Ok(SGenericActorType::Id(id)), + Err(_) => Ok(SGenericActorType::Name(id.to_string())), + } + } + } + + deserializer.deserialize_any(IdVisitor) + } +} + +impl From for SUserType { + fn from(id: u32) -> Self { + SUserType(id.into()) + } +} + +impl From for SGroupType { + fn from(id: u32) -> Self { + SGroupType(id.into()) + } +} + +impl From<&str> for SUserType { + fn from(name: &str) -> Self { + SUserType(name.into()) + } +} + +impl From<&str> for SGroupType { + fn from(name: &str) -> Self { + SGroupType(name.into()) + } +} + +impl From for SGroupType { + fn from(group: Group) -> Self { + SGroupType(SGenericActorType::Id(group.gid.as_raw())) + } +} + +impl From<&str> for SGenericActorType { + fn from(name: &str) -> Self { + SGenericActorType::Name(name.into()) + } +} + +impl From for SGenericActorType { + fn from(id: u32) -> Self { + SGenericActorType::Id(id) + } +} + +impl PartialEq for SUserType { + fn eq(&self, other: &User) -> bool { + let uid = self.fetch_id(); + match uid { + Some(uid) => uid == other.uid.as_raw(), + None => false, + } + } +} + +impl PartialEq for SUserType { + fn eq(&self, other: &str) -> bool { + self.eq(&SUserType::from(other)) + } +} + +impl PartialEq for SGroupType { + fn eq(&self, other: &str) -> bool { + self.eq(&SGroupType::from(other)) + } +} + +impl PartialEq for SUserType { + fn eq(&self, other: &u32) -> bool { + self.eq(&SUserType::from(*other)) + } +} + +impl PartialEq for SGroupType { + fn eq(&self, other: &u32) -> bool { + self.eq(&SGroupType::from(*other)) + } +} + +impl PartialEq for SGroupType { + fn eq(&self, other: &Group) -> bool { + let gid = self.fetch_id(); + match gid { + Some(gid) => gid == other.gid.as_raw(), + None => false, + } + } +} + +impl PartialEq<[SGroupType; N]> for SGroups { + fn eq(&self, other: &[SGroupType; N]) -> bool { + match self { + SGroups::Single(group) => { + if N == 1 { + group == &other[0] + } else { + false + } + } + SGroups::Multiple(groups) => { + if groups.len() == N { + groups.iter().zip(other.iter()).all(|(a, b)| a == b) + } else { + false + } + } + } + } +} + +impl From<[SGroupType; N]> for SGroups { + fn from(groups: [SGroupType; N]) -> Self { + if N == 1 { + SGroups::Single(groups[0].to_owned()) + } else { + SGroups::Multiple(groups.iter().map(|x| x.to_owned()).collect()) + } + } +} + +impl FromIterator for SGroups { + fn from_iter>(iter: I) -> Self { + let mut iter = iter.into_iter(); + let first = iter.next().unwrap(); + let mut groups: Vec = vec![first.as_str().into()]; + for group in iter { + groups.push(group.as_str().into()); + } + if groups.len() == 1 { + SGroups::Single(groups[0].to_owned()) + } else { + SGroups::Multiple(groups) + } + } +} + +impl From<[&str; N]> for SGroups { + fn from(groups: [&str; N]) -> Self { + if N == 1 { + SGroups::Single(groups[0].into()) + } else { + SGroups::Multiple(groups.iter().map(|&x| x.into()).collect()) + } + } +} + +impl From> for SGroups { + fn from(groups: Vec) -> Self { + if groups.len() == 1 { + SGroups::Single(groups[0].into()) + } else { + SGroups::Multiple(groups.into_iter().map(|x| x.into()).collect()) + } + } +} + +impl From> for SGroups { + fn from(groups: Vec) -> Self { + if groups.len() == 1 { + SGroups::Single(groups[0].clone()) + } else { + SGroups::Multiple(groups) + } + } +} + +impl From for SGroups { + fn from(group: u32) -> Self { + SGroups::Single(group.into()) + } +} + +impl From<&str> for SGroups { + fn from(group: &str) -> Self { + SGroups::Single(group.into()) + } +} + +impl PartialEq> for SGroups { + fn eq(&self, other: &Vec) -> bool { + match self { + SGroups::Single(actor) => { + if other.len() == 1 { + return actor == &other[0]; + } + } + SGroups::Multiple(actors) => { + if actors.len() == other.len() { + return actors.iter().all(|actor| other.iter().any(|x| actor == x)); + } + } + } + false + } +} + +impl core::fmt::Display for SGroups { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SGroups::Single(group) => { + write!(f, "{}", group) + } + SGroups::Multiple(groups) => { + write!(f, "{:?}", groups) + } + } + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs)] +#[serde(tag = "type", rename_all = "lowercase")] +pub enum SActor { + #[serde(rename = "user")] + User { + #[serde(alias = "name", skip_serializing_if = "Option::is_none")] + id: Option, + #[serde(default, flatten, skip_serializing_if = "Map::is_empty")] + _extra_fields: Map, + }, + #[serde(rename = "group")] + Group { + #[serde(alias = "names", skip_serializing_if = "Option::is_none")] + groups: Option, + #[serde(default, flatten)] + _extra_fields: Map, + }, + #[serde(untagged)] + Unknown(Value), +} + +#[bon] +impl SActor { + #[builder(finish_fn = build)] + pub fn user( + #[builder(start_fn, into)] id: SUserType, + #[builder(default, with = <_>::from_iter)] _extra_fields: Map, + ) -> Self { + SActor::User { + id: Some(id), + _extra_fields, + } + } + #[builder(finish_fn = build)] + pub fn group( + #[builder(start_fn, into)] groups: SGroups, + #[builder(default, with = <_>::from_iter)] _extra_fields: Map, + ) -> Self { + SActor::Group { + groups: Some(groups), + _extra_fields, + } + } +} + +impl core::fmt::Display for SActor { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + SActor::User { id, _extra_fields } => { + write!(f, "User: {}", id.as_ref().unwrap()) + } + SActor::Group { + groups, + _extra_fields, + } => { + write!(f, "Group: {}", groups.as_ref().unwrap()) + } + SActor::Unknown(unknown) => { + write!(f, "Unknown: {}", unknown) + } + } + } +} diff --git a/rar-common/src/database/finder.rs b/rar-common/src/database/finder.rs index f71e4f79..4c99e317 100644 --- a/rar-common/src/database/finder.rs +++ b/rar-common/src/database/finder.rs @@ -17,13 +17,13 @@ use nix::{ }; #[cfg(feature = "pcre2")] use pcre2::bytes::RegexBuilder; + use strum::EnumIs; use crate::database::{ + actor::SActor, options::{Opt, OptStack}, - structs::{ - SActor, SActorType, SCommand, SCommands, SConfig, SGroups, SRole, STask, SetBehavior, - }, + structs::{SCommand, SCommands, SConfig, SRole, STask, SUserChooser, SetBehavior}, }; use crate::util::{capabilities_are_exploitable, final_path, parse_conf_command}; use crate::{ @@ -32,19 +32,22 @@ use crate::{ }; use bitflags::bitflags; -use super::options::EnvBehavior; +use super::{ + actor::{SGroupType, SGroups, SUserType}, + FilterMatcher, +}; -#[derive(Debug, PartialEq, Eq, Clone, Copy)] +#[derive(Debug, PartialEq, Eq, Clone, EnumIs)] pub enum MatchError { - NoMatch, - Conflict, + NoMatch(String), + Conflict(String), } impl Display for MatchError { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self { - MatchError::NoMatch => write!(f, "No match"), - MatchError::Conflict => write!(f, "Conflict"), + MatchError::NoMatch(reason) => write!(f, "No match because : {}", reason), + MatchError::Conflict(reason) => write!(f, "Conflict because : {}", reason), } } } @@ -52,8 +55,8 @@ impl Display for MatchError { impl Error for MatchError { fn description(&self) -> &str { match self { - MatchError::NoMatch => "No match", - MatchError::Conflict => "Conflict", + MatchError::NoMatch(_) => "No match", + MatchError::Conflict(_) => "Conflict", } } } @@ -63,7 +66,7 @@ pub struct ExecSettings { pub exec_path: PathBuf, pub exec_args: Vec, pub opt: OptStack, - pub setuid: Option, + pub setuid: Option, pub setgroups: Option, pub caps: Option, pub task: Weak>, @@ -124,25 +127,53 @@ impl PartialEq for ExecSettings { #[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Debug, EnumIs)] #[repr(u32)] -pub enum UserMin { +// Matching user groups for the role +pub enum ActorMatchMin { UserMatch, GroupMatch(usize), NoMatch, } #[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Debug)] -#[repr(u32)] -pub enum SetuidMin { - Undefined, - NoSetuidNoSetgid, - Setgid(usize), - Setuid, - SetuidSetgid(usize), - SetgidRoot(usize), - SetuidNotrootSetgidRoot(usize), - SetuidRoot, - SetuidRootSetgid(usize), - SetuidSetgidRoot(usize), + +// Matching setuid and setgid for the role +struct SetuidMin { + is_root: bool, +} +#[derive(PartialEq, Eq, Clone, Copy, Debug)] +struct SetgidMin { + is_root: bool, + nb_groups: usize, +} +impl PartialOrd for SetgidMin { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for SetgidMin { + fn cmp(&self, other: &Self) -> Ordering { + self.is_root + .cmp(&other.is_root) + .then_with(|| self.nb_groups.cmp(&other.nb_groups)) + } +} + +#[derive(PartialEq, Eq, Clone, Copy, Debug, Default)] +pub struct SetUserMin { + uid: Option, + gid: Option, +} +impl PartialOrd for SetUserMin { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Ord for SetUserMin { + fn cmp(&self, other: &Self) -> Ordering { + self.uid + .cmp(&other.uid) + .then_with(|| self.gid.cmp(&other.gid)) + } } #[derive(PartialEq, PartialOrd, Eq, Ord, Clone, Copy, Debug)] @@ -185,10 +216,10 @@ bitflags! { #[derive(PartialEq, Eq, Clone, Copy, Debug)] pub struct Score { - pub user_min: UserMin, + pub user_min: ActorMatchMin, pub cmd_min: CmdMin, pub caps_min: CapsMin, - pub setuid_min: SetuidMin, + pub setuser_min: SetUserMin, pub security_min: SecurityMin, } @@ -196,7 +227,7 @@ impl Score { pub fn prettyprint(&self) -> String { format!( "{:?}, {:?}, {:?}, {:?}, {:?}", - self.user_min, self.cmd_min, self.caps_min, self.setuid_min, self.security_min + self.user_min, self.cmd_min, self.caps_min, self.setuser_min, self.security_min ) } @@ -209,7 +240,7 @@ impl Score { self.cmd_min .cmp(&other.cmd_min) .then(self.caps_min.cmp(&other.caps_min)) - .then(self.setuid_min.cmp(&other.setuid_min)) + .then(self.setuser_min.cmp(&other.setuser_min)) .then(self.security_min.cmp(&other.security_min)) } } @@ -289,7 +320,7 @@ impl TaskMatch { } pub fn user_matching(&self) -> bool { - self.score.user_min != UserMin::NoMatch + self.score.user_min != ActorMatchMin::NoMatch } pub fn command_matching(&self) -> bool { @@ -313,10 +344,10 @@ impl Default for TaskMatch { fn default() -> Self { TaskMatch { score: Score { - user_min: UserMin::NoMatch, + user_min: ActorMatchMin::NoMatch, cmd_min: CmdMin::empty(), caps_min: CapsMin::Undefined, - setuid_min: SetuidMin::Undefined, + setuser_min: SetUserMin::default(), security_min: SecurityMin::empty(), }, settings: ExecSettings::new(), @@ -324,13 +355,7 @@ impl Default for TaskMatch { } } -#[derive(Debug, Default, Builder)] -#[builder(on(_, overwritable))] -pub struct FilterMatcher { - pub role: Option, - pub task: Option, - pub env_behavior: Option, -} + pub trait TaskMatcher { fn matches( @@ -342,7 +367,7 @@ pub trait TaskMatcher { } pub trait CredMatcher { - fn user_matches(&self, user: &Cred) -> UserMin; + fn user_matches(&self, user: &Cred) -> ActorMatchMin; } fn find_from_envpath(needle: &PathBuf) -> Option { @@ -359,7 +384,7 @@ fn find_from_envpath(needle: &PathBuf) -> Option { None } -fn match_path(input_path: &String, role_path: &String) -> CmdMin { +fn match_path(input_path: &str, role_path: &String) -> CmdMin { if role_path == "**" { return CmdMin::FullWildcardPath; } @@ -409,7 +434,7 @@ fn evaluate_regex_cmd(role_args: String, commandline: String) -> Result>>) -> SecurityMin { } } -fn is_root(actortype: &SActorType) -> bool { - match actortype { - SActorType::Id(id) => *id == 0, - SActorType::Name(name) => name == "root", - } +fn group_is_root(actortype: &SGroupType) -> bool { + (*actortype).fetch_id().map_or(false, |id| id == 0) +} + +fn user_is_root(actortype: &SUserType) -> bool { + (*actortype).fetch_id().map_or(false, |id| id == 0) } fn groups_contains_root(list: Option<&SGroups>) -> bool { if let Some(list) = list { match list { - SGroups::Single(group) => is_root(&group), - SGroups::Multiple(groups) => groups.iter().any(is_root), + SGroups::Single(group) => group_is_root(group), + SGroups::Multiple(groups) => groups.iter().any(group_is_root), } } else { false @@ -534,52 +560,100 @@ fn groups_contains_root(list: Option<&SGroups>) -> bool { fn groups_len(groups: Option<&SGroups>) -> usize { match groups { - Some(groups) => match groups { - SGroups::Single(_) => 1, - SGroups::Multiple(groups) => groups.len(), - }, + Some(groups) => groups.len(), None => 0, } } fn get_setuid_min( - setuid: Option<&SActorType>, + setuid: Option<&SUserType>, setgid: Option<&SGroups>, security_min: &SecurityMin, -) -> SetuidMin { +) -> SetUserMin { match (setuid, setgid) { (Some(setuid), setgid) => { if security_min.contains(SecurityMin::EnableRoot) { // root is privileged - if is_root(setuid) { + if user_is_root(setuid) { if groups_contains_root(setgid) { - SetuidMin::SetuidSetgidRoot(groups_len(setgid)) + SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: (groups_len(setgid)), + }), + } } else if setgid.is_none() || groups_len(setgid) == 0 { - SetuidMin::SetuidRoot + SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: None, + } } else { - SetuidMin::SetuidRootSetgid(groups_len(setgid)) + SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: Some(SetgidMin { + is_root: false, + nb_groups: (groups_len(setgid)), + }), + } } } else if groups_contains_root(setgid) { - SetuidMin::SetuidNotrootSetgidRoot(groups_len(setgid)) + SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: (groups_len(setgid)), + }), + } } else if setgid.is_none() || groups_len(setgid) == 0 { - SetuidMin::Setuid + SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: None, + } } else { - SetuidMin::SetuidSetgid(groups_len(setgid)) + SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: Some(SetgidMin { + is_root: false, + nb_groups: (groups_len(setgid)), + }), + } } } else { // root is a user - SetuidMin::SetuidSetgid(groups_len(setgid)) + SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: Some(SetgidMin { + is_root: false, + nb_groups: (groups_len(setgid)), + }), + } } } (None, setgid) => { let len = groups_len(setgid); if len == 0 { - SetuidMin::NoSetuidNoSetgid + SetUserMin { + uid: None, + gid: None, + } } else if security_min.contains(SecurityMin::EnableRoot) && groups_contains_root(setgid) { - SetuidMin::SetgidRoot(len) + SetUserMin { + uid: None, + gid: Some(SetgidMin { + is_root: true, + nb_groups: len, + }), + } } else { - SetuidMin::Setgid(len) + SetUserMin { + uid: None, + gid: Some(SetgidMin { + is_root: false, + nb_groups: len, + }), + } } } } @@ -596,11 +670,13 @@ impl TaskMatcher for Rc> { if let Some(task) = &cmd_opt.task { if task != &self.as_ref().borrow().name.to_string() { debug!("Task {} does not match", self.as_ref().borrow().name); - return Err(MatchError::NoMatch); + return Err(MatchError::NoMatch("Task name does not match".to_string())); } } } debug!("Matching task {}", self.as_ref().borrow().name); + + // Match initial task commands let TaskMatch { mut score, mut settings, @@ -609,6 +685,8 @@ impl TaskMatcher for Rc> { .borrow() .commands .matches(user, cmd_opt, command)?; + + // Process capabilities and security let capset = self .as_ref() .borrow() @@ -630,23 +708,78 @@ impl TaskMatcher for Rc> { .borrow() .env .as_ref() - .is_some_and(|env| !env.override_behavior || env.default_behavior == *behavior) + .is_some_and(|env| !env.override_behavior.is_some_and(|b| b) || env.default_behavior == *behavior) // but the polcy deny it and the behavior is not the same as the default one // we return NoMatch // (explaination: if the behavior is the same as the default one, we don't override it) }) { - return Err(MatchError::NoMatch); + return Err(MatchError::NoMatch("The user wants to override the behavior but the policy deny it".to_string())); } - let setuid = &self.as_ref().borrow().cred.setuid; + // Processing setuid + let setuid: Option = self.as_ref().borrow().cred.setuid.clone(); + let setuid_result = match setuid { + Some(SUserChooser::Actor(s)) => Some(s), + Some(SUserChooser::ChooserStruct(t)) => { + match cmd_opt.as_ref().and_then(|cmd| cmd.user.as_ref()) { + None => { + debug!( + "Aucun utilisateur spécifié dans la commande, fallback utilisé : {:?}", + t.fallback + ); + Some(t.fallback.clone()) // Retourne le fallback si aucun utilisateur n'est spécifié + } + Some(user) => { + debug!("Utilisateur spécifié dans la commande : {}", user); + + // Comparer l'utilisateur spécifié avec le fallback + if user.fetch_eq(&t.fallback) { + debug!( + "L'utilisateur spécifié dans la commande correspond au fallback !" + ); + Some(t.fallback.clone()) // Si l'utilisateur correspond au fallback, utiliser le fallback + } else if t.sub.iter().any(|s| s.fetch_eq(user)) { + // Si l'utilisateur est explicitement interdit dans `sub` + return Err(MatchError::NoMatch("L'utilisateur est interdit dans sub.".into())); + } else if t.add.iter().any(|s| s.fetch_eq(user)) { + // Si l'utilisateur est explicitement autorisé dans `add` + + Some(user.clone()) // Retourner une erreur immédiate + } else { + // Aucun match explicite, appliquer le comportement par défaut + match t.default { + SetBehavior::None => { + return Err(MatchError::NoMatch("Aucun comportement par défaut applicable.".into())); // Aucun utilisateur par défaut + } + SetBehavior::All => { + debug!("Tous les utilisateurs sont acceptés."); + Some(user.clone()) // Tout utilisateur accepté + } + } + } + } + } + } + None => None, + }; + + // Set gid processing let setgid = &self.as_ref().borrow().cred.setgid; - score.setuid_min = get_setuid_min(setuid.as_ref(), setgid.as_ref(), &score.security_min); - settings.setuid = setuid.clone(); + // Calculate setuid and setgid minimum + score.setuser_min = + get_setuid_min(setuid_result.as_ref(), setgid.as_ref(), &score.security_min); + + // Update task settings + settings.setuid = setuid_result.clone(); settings.setgroups = setgid.clone(); settings.caps = capset; + + // Get options stack from the task let stack = OptStack::from_task(self.clone()); settings.opt = stack; + + // Return the final TaskMatch Ok(TaskMatch { score, settings }) } } @@ -672,7 +805,7 @@ impl TaskMatcher for SCommands { let is_forbidden = get_cmd_min(input_command, &self.sub); if !is_forbidden.is_empty() { debug!("Command is forbidden"); - return Err(MatchError::NoMatch); + return Err(MatchError::NoMatch("Command is forbidden".to_string())); } // otherwise, we check if behavior is No command allowed by default if get_default_behavior(&self.default_behavior).is_none() { @@ -680,7 +813,7 @@ impl TaskMatcher for SCommands { // if the behavior is No command by default, we check if the command is allowed explicitly. min_score = get_cmd_min(input_command, &self.add); if min_score.is_empty() { - return Err(MatchError::NoMatch); + return Err(MatchError::NoMatch("Command is not allowed".to_string())); } } else { min_score = CmdMin::all(); @@ -700,10 +833,10 @@ impl TaskMatcher for SCommands { Ok(TaskMatch { score: Score { - user_min: UserMin::NoMatch, + user_min: ActorMatchMin::NoMatch, cmd_min: min_score, caps_min: CapsMin::Undefined, - setuid_min: SetuidMin::Undefined, + setuser_min: SetUserMin::default(), security_min: SecurityMin::empty(), }, settings, @@ -736,30 +869,30 @@ fn match_groups(groups: &[Group], role_groups: &[SGroups]) -> bool { } impl CredMatcher for Rc> { - fn user_matches(&self, user: &Cred) -> UserMin { + fn user_matches(&self, user: &Cred) -> ActorMatchMin { let borrow = self.as_ref().borrow(); if PluginManager::notify_duty_separation(&self.as_ref().borrow(), user).is_deny() { warn!("You are forbidden to use a role due to a conflict of interest, please contact your administrator"); - return UserMin::NoMatch; + return ActorMatchMin::NoMatch; } let matches = borrow.actors.iter().filter_map(|actor| { match actor { SActor::User { id, .. } => { if let Some(id) = id { if *id == user.user { - return Some(UserMin::UserMatch); + return Some(ActorMatchMin::UserMatch); } } } SActor::Group { groups, .. } => { if let Some(groups) = groups.as_ref() { if match_groups(&user.groups, &[groups.clone()]) { - return Some(UserMin::GroupMatch(groups.len())); + return Some(ActorMatchMin::GroupMatch(groups.len())); } } } SActor::Unknown(element) => { - let min = PluginManager::notify_user_matcher(&as_borrow!(self), user, &element); + let min = PluginManager::notify_user_matcher(&as_borrow!(self), user, element); if !min.is_no_match() { return Some(min); } @@ -767,7 +900,7 @@ impl CredMatcher for Rc> { } None }); - let min = matches.min().unwrap_or(UserMin::NoMatch); + let min = matches.min().unwrap_or(ActorMatchMin::NoMatch); debug!( "Role {} : User {} matches with {:?}", borrow.name, user.user.name, min @@ -802,10 +935,10 @@ impl TaskMatcher for Vec>> { } } Err(err) => match err { - MatchError::NoMatch => { + MatchError::NoMatch(_) => { continue; } - MatchError::Conflict => { + MatchError::Conflict(_) => { return Err(err); } }, @@ -813,11 +946,11 @@ impl TaskMatcher for Vec>> { } debug!("nmatch = {}", nmatch); if nmatch == 0 { - Err(MatchError::NoMatch) + Err(MatchError::NoMatch("No tasks matched".into())) } else if nmatch == 1 { Ok(min_task) } else { - Err(MatchError::Conflict) + Err(MatchError::Conflict("Multiple tasks matched".into())) } } } @@ -848,7 +981,7 @@ impl TaskMatcher for Vec>> { } } Err(err) => { - if err == MatchError::NoMatch { + if err.is_no_match() { continue; } else { return Err(err); @@ -857,11 +990,11 @@ impl TaskMatcher for Vec>> { } } if nmatch == 0 { - Err(MatchError::NoMatch) + Err(MatchError::NoMatch("No roles matched".into())) } else if nmatch == 1 { Ok(min_role) } else { - Err(MatchError::Conflict) + Err(MatchError::Conflict("Multiple roles matched".into())) } } } @@ -876,7 +1009,7 @@ impl TaskMatcher for Rc> { if let Some(cmd_opt) = cmd_opt { if let Some(role) = &cmd_opt.role { if role != &self.as_ref().borrow().name { - return Err(MatchError::NoMatch); + return Err(MatchError::NoMatch("Role name does not match".to_string())); } } } @@ -896,11 +1029,11 @@ impl TaskMatcher for Rc> { nmatch = 1; } } - Err(MatchError::NoMatch) => { + Err(MatchError::NoMatch(_)) => { nmatch = 0; } - Err(MatchError::Conflict) => { - return Err(MatchError::Conflict); + Err(MatchError::Conflict(msg)) => { + return Err(MatchError::Conflict(msg)); } } min_role.score.user_min = user_min; @@ -919,7 +1052,7 @@ impl TaskMatcher for Rc> { min_role.score.prettyprint() ); if nmatch == 0 { - Err(MatchError::NoMatch) + Err(MatchError::NoMatch("No tasks matched".into())) } else if nmatch == 1 { debug!( "=== Role {} === : Match for task {}\nScore : {}", @@ -929,13 +1062,13 @@ impl TaskMatcher for Rc> { ); Ok(min_role) } else { - Err(MatchError::Conflict) + Err(MatchError::Conflict("Multiple tasks matched".into())) } } } fn plugin_role_match( - user_min: UserMin, + user_min: ActorMatchMin, borrow: std::cell::Ref<'_, SRole>, user: &Cred, cmd_opt: &Option, @@ -999,9 +1132,9 @@ impl TaskMatcher for Rc> { } // we ignore error, because it's not a match } if tasks.is_empty() { - Err(MatchError::NoMatch) + Err(MatchError::NoMatch("No roles matched".into())) } else if tasks.len() > 1 { - Err(MatchError::Conflict) + Err(MatchError::Conflict("Multiple roles matched".into())) } else { debug!( "Config : Matched user {}\n - command {:?}\n - with task {}\n - with role {}\n - with score {:?}", @@ -1028,7 +1161,7 @@ mod tests { database::{ make_weak_config, options::{EnvBehavior, PathBehavior, SAuthentication, SBounding, SPrivileged}, - structs::IdTask, + structs::{IdTask, RoleGetter, SCredentials, SSetuidSet}, versionning::Versioning, }, rc_refcell, @@ -1036,6 +1169,20 @@ mod tests { use super::*; + fn get_non_root_uid() -> u32 { + // list all users + let passwd = fs::read_to_string("/etc/passwd").unwrap(); + let passwd: Vec<&str> = passwd.split('\n').collect(); + return passwd + .iter() + .map(|line| { + let line: Vec<&str> = line.split(':').collect(); + line[2].parse::().unwrap() + }) + .find(|uid| *uid != 0) + .unwrap(); + } + #[test] fn test_match_path() { let result = match_path(&"/bin/ls".to_string(), &"/bin/ls".to_string()); @@ -1138,9 +1285,12 @@ mod tests { #[test] fn test_is_root() { - assert!(is_root(&"root".into())); - assert!(is_root(&0.into())); - assert!(!is_root(&1.into())); + assert!(user_is_root(&"root".into())); + assert!(user_is_root(&0.into())); + assert!(!user_is_root(&1.into())); + assert!(group_is_root(&"root".into())); + assert!(group_is_root(&0.into())); + assert!(!group_is_root(&1.into())); } #[test] @@ -1155,51 +1305,93 @@ mod tests { #[test] fn test_get_setuid_min() { - let mut setuid: Option = Some("root".into()); + let mut setuid: Option = Some("root".into()); let mut setgid = Some(SGroups::Single("root".into())); let security_min = SecurityMin::EnableRoot; assert_eq!( get_setuid_min(setuid.as_ref(), setgid.as_ref(), &security_min), - SetuidMin::SetuidSetgidRoot(1) + SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: 1 + }) + } ); setuid = Some("1".into()); assert_eq!( get_setuid_min(setuid.as_ref(), setgid.as_ref(), &security_min), - SetuidMin::SetuidNotrootSetgidRoot(1) + SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: 1 + }) + } ); setgid = Some(SGroups::Multiple(vec![1.into(), 2.into()])); assert_eq!( get_setuid_min(setuid.as_ref(), setgid.as_ref(), &security_min), - SetuidMin::SetuidSetgid(2) + SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: Some(SetgidMin { + is_root: false, + nb_groups: 2 + }) + } ); assert_eq!( get_setuid_min(None, setgid.as_ref(), &security_min), - SetuidMin::Setgid(2) + SetUserMin { + uid: None, + gid: Some(SetgidMin { + is_root: false, + nb_groups: 2 + }) + } ); assert_eq!( get_setuid_min(None, None, &security_min), - SetuidMin::NoSetuidNoSetgid + SetUserMin { + uid: None, + gid: None + } ); assert_eq!( get_setuid_min(setuid.as_ref(), None, &security_min), - SetuidMin::Setuid + SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: None + } ) } #[test] fn test_score_cmp() { let score1 = Score { - user_min: UserMin::UserMatch, + user_min: ActorMatchMin::UserMatch, cmd_min: CmdMin::Match, caps_min: CapsMin::CapsAll, - setuid_min: SetuidMin::SetuidSetgidRoot(1), + setuser_min: SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: 1, + }), + }, security_min: SecurityMin::DisableBounding | SecurityMin::EnableRoot, }; let mut score2 = Score { - user_min: UserMin::UserMatch, + user_min: ActorMatchMin::UserMatch, cmd_min: CmdMin::Match, caps_min: CapsMin::CapsAll, - setuid_min: SetuidMin::SetuidSetgidRoot(1), + setuser_min: SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: 1, + }), + }, security_min: SecurityMin::DisableBounding, }; assert_eq!(score1.cmp(&score2), Ordering::Greater); @@ -1210,15 +1402,46 @@ mod tests { assert_eq!(score1.clamp(score2, score2), score2); score2.security_min = SecurityMin::DisableBounding | SecurityMin::EnableRoot; assert_eq!(score1.cmp(&score2), Ordering::Equal); - score2.setuid_min = SetuidMin::SetuidSetgidRoot(2); + score2.setuser_min = SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: 2, + }), + }; assert_eq!(score1.cmp(&score2), Ordering::Less); - score2.setuid_min = SetuidMin::SetuidNotrootSetgidRoot(2); + score2.setuser_min = SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: 2, + }), + }; assert_eq!(score1.cmp(&score2), Ordering::Greater); - score2.setuid_min = SetuidMin::SetuidRootSetgid(2); + score2.setuser_min = SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: Some(SetgidMin { + is_root: false, + nb_groups: 2, + }), + }; assert_eq!(score1.cmp(&score2), Ordering::Greater); - score2.setuid_min = SetuidMin::SetuidSetgid(2); + score2.setuser_min = SetUserMin { + uid: Some(SetuidMin { is_root: false }), + gid: Some(SetgidMin { + is_root: false, + nb_groups: 2, + }), + }; assert_eq!(score1.cmp(&score2), Ordering::Greater); - score2.setuid_min = SetuidMin::SetuidSetgidRoot(1); + score2.setuser_min = SetUserMin { + uid: Some(SetuidMin { is_root: true }), + gid: Some(SetgidMin { + is_root: true, + nb_groups: 1, + }), + }; + assert_eq!(score1.cmp(&score2), Ordering::Equal); score2.caps_min = CapsMin::CapsAdmin(1); assert_eq!(score1.cmp(&score2), Ordering::Greater); score2.caps_min = CapsMin::CapsNoAdmin(1); @@ -1237,23 +1460,18 @@ mod tests { assert_eq!(score1.cmp(&score2), Ordering::Less); score2.cmd_min = CmdMin::Match; assert_eq!(score1.cmp(&score2), Ordering::Equal); - score2.user_min = UserMin::GroupMatch(1); + score2.user_min = ActorMatchMin::GroupMatch(1); assert_eq!(score1.cmp(&score2), Ordering::Less); - score2.user_min = UserMin::NoMatch; + score2.user_min = ActorMatchMin::NoMatch; assert_eq!(score1.cmp(&score2), Ordering::Less); - score2.user_min = UserMin::UserMatch; + score2.user_min = ActorMatchMin::UserMatch; assert_eq!(score1.cmp(&score2), Ordering::Equal); } fn setup_test_config(num_roles: usize) -> Rc> { - let config = Rc::new(SConfig::default().into()); - for i in 0..num_roles { - let mut role = SRole::default(); - role.name = format!("role{}", i); - role._config = Some(Rc::downgrade(&config)); - config.as_ref().borrow_mut().roles.push(rc_refcell!(role)); - } - config + SConfig::builder() + .roles((0..num_roles).map(|i| SRole::builder(format!("role{}", i)).build())) + .build() } fn setup_test_role( @@ -1291,12 +1509,12 @@ mod tests { .as_ref() .borrow_mut() .actors - .push(SActor::from_user_string("root")); + .push(SActor::user("root").build()); role1 .as_ref() .borrow_mut() .actors - .push(SActor::from_user_string("root")); + .push(SActor::user("root").build()); r0_task0 .as_ref() @@ -1330,14 +1548,13 @@ mod tests { r1_task1.as_ref().borrow_mut().cred.capabilities = Some(capset.into()); let cred = Cred { - user: User::from_name("root").unwrap().unwrap(), + user: User::from_uid(Uid::from_raw(0)).unwrap().unwrap(), groups: vec![Group::from_name("root").unwrap().unwrap()], ppid: Pid::from_raw(0), tty: None, }; let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; - let result = config.matches(&cred, &None, &command); debug!("Result : {:?}", result); assert!(result.is_ok()); @@ -1349,6 +1566,403 @@ mod tests { assert_eq!(result.role().as_ref().borrow().name, "role0"); } + #[test] + + fn test_setuid_fallback_valid() { + // Configuration de test + let config = setup_test_config(1); // Un seul rôle pour simplifier + let role = setup_test_role(1, Some(config.as_ref().borrow().roles[0].clone()), None); + let task = role.as_ref().borrow().tasks[0].clone(); + + // Ajout d'un acteur autorisé + role.as_ref() + .borrow_mut() + .actors + .push(SActor::user("root").build()); + + task.as_ref().borrow_mut().commands.default_behavior = Some(SetBehavior::All); + + // Définition du `setuid` avec un `fallback` + let fallback_user = SUserType::from(get_non_root_uid()); + let chooser_struct = SSetuidSet { + fallback: fallback_user.clone(), + default: SetBehavior::None, + add: vec![], // Pas d'ajout explicite + sub: vec![], // Pas de restriction explicite + }; + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::ChooserStruct(chooser_struct)); + + // Création des credentials avec l'utilisateur correspondant au `fallback` + let cred = Cred::builder().user_name("root").group_name("root").build(); + + // Commande de test + let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; + // Exécution du match + let filter_matcher = FilterMatcher::builder().user(fallback_user.clone()).build(); + + let result = config.matches(&cred, &Some(filter_matcher), &command); + println!("Résultat matches: {:?}", result); + + // Vérification que le match est réussi + assert!(result.is_ok()); + let result = result.unwrap(); + + // Vérification que l'utilisateur assigné est bien celui du fallback + assert_eq!(result.settings.setuid, Some(fallback_user.clone())); + + println!("Test réussi : L'utilisateur spécifié correspond bien au fallback."); + } + + #[test] + + fn test_setuid_fallback_nonarg_valid() { + // Configuration de test + let config = setup_test_config(1); + let role = setup_test_role(1, Some(config.as_ref().borrow().roles[0].clone()), None); + let task = role.as_ref().borrow().tasks[0].clone(); + + // Ajout d'un acteur autorisé + role.as_ref() + .borrow_mut() + .actors + .push(SActor::user("root").build()); + + task.as_ref().borrow_mut().commands.default_behavior = Some(SetBehavior::All); + + // Définition du `setuid` avec un `fallback` + let fallback_user = SUserType::from(get_non_root_uid()); + let chooser_struct = SSetuidSet { + fallback: fallback_user.clone(), + default: SetBehavior::None, + add: vec![], + sub: vec![], + }; + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::ChooserStruct(chooser_struct)); + + // Création des credentials sans spécifier d'utilisateur + let cred = Cred { + user: User::from_name("root").unwrap().unwrap(), // Utilisateur non spécifié + groups: vec![Group::from_name("root").unwrap().unwrap()], + ppid: Pid::from_raw(0), + tty: None, + }; + + // Commande de test + let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; + + // Exécution du match + let result = config.matches(&cred, &None, &command); + + // Vérification que le match est réussi + assert!(result.is_ok()); + let result = result.unwrap(); + + // Vérification que l'utilisateur assigné est bien celui du fallback + assert_eq!(result.settings.setuid, Some(fallback_user.clone())); + + println!("Test réussi : L'utilisateur spécifié correspond bien au fallback lorsqu'aucun utilisateur valide n'est fourni."); + } + + #[test] + + fn test_setuid_add_valid() { + // Configuration de test + let config = setup_test_config(1); // Un seul rôle pour simplifier + let role = setup_test_role(1, Some(config.as_ref().borrow().roles[0].clone()), None); + let task = role.as_ref().borrow().tasks[0].clone(); + + // Ajout d'un acteur autorisé + role.as_ref() + .borrow_mut() + .actors + .push(SActor::user("root").build()); + + task.as_ref().borrow_mut().commands.default_behavior = Some(SetBehavior::All); + + // Définition du `setuid` avec un `fallback` + let fallback_user = SUserType::from(get_non_root_uid()); + let chooser_struct = SSetuidSet { + fallback: fallback_user.clone(), + default: SetBehavior::None, + add: vec![SUserType::from("root")], // Ajout d'un utilisateur + sub: vec![], // Pas de restriction explicite + }; + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::ChooserStruct(chooser_struct)); + + // Création des credentials avec l'utilisateur correspondant à l'ajout + let cred = Cred { + user: User::from_name("root").unwrap().unwrap(), // Même nom que l'ajout + groups: vec![Group::from_name("root").unwrap().unwrap()], + ppid: Pid::from_raw(0), + tty: None, + }; + + // Commande de test + let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; + + // Exécution du match + let filter_matcher = FilterMatcher::builder().user("root").build(); + + let result = config.matches(&cred, &Some(filter_matcher), &command); + + // Vérification que le match est réussi + assert!(result.is_ok()); + let result = result.unwrap(); + + // Vérification que l'utilisateur assigné est bien celui de l'ajout + assert_eq!(result.settings.setuid, Some(SUserType::from("root"))); + + println!("Test réussi : L'utilisateur spécifié correspond bien à l'ajout."); + } + + #[test] + fn test_setuid_add_sub_invalid() { + // Configuration de test + let config = setup_test_config(1); // Un seul rôle pour simplifier + let role = setup_test_role(1, Some(config.as_ref().borrow().roles[0].clone()), None); + let task = role.as_ref().borrow().tasks[0].clone(); + + // Ajout d'un acteur autorisé + role.as_ref() + .borrow_mut() + .actors + .push(SActor::user("root").build()); + + task.as_ref().borrow_mut().commands.default_behavior = Some(SetBehavior::All); + + // Définition du `setuid` avec un `fallback` + let fallback_user = SUserType::from(1); + let chooser_struct = SSetuidSet { + fallback: fallback_user.clone(), + default: SetBehavior::None, + add: vec![SUserType::from("root")], // Ajout d'un utilisateur + sub: vec![SUserType::from("root")], // Restriction d'un utilisateur + }; + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::ChooserStruct(chooser_struct)); + + // Création des credentials avec l'utilisateur correspondant à l'ajout + let cred = Cred { + user: User::from_name("root").unwrap().unwrap(), // Même nom que l'ajout + groups: vec![Group::from_name("root").unwrap().unwrap()], + ppid: Pid::from_raw(0), + tty: None, + }; + + // Commande de test + let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; + // Exécution du match + let filter_matcher = FilterMatcher::builder().user("root").build(); + + let result = config.matches(&cred, &Some(filter_matcher), &command); + println!("Résultat matches: {:?}", result); + // Vérification que le match est réussi + assert!(result.is_err()); + let result = result.unwrap_err(); + + // Vérification que l'erreur est bien de type `NoMatch` + assert!(result.is_no_match()); + + println!("Test réussi : L'utilisateur spécifié ne correspond pas à la restriction."); + } + + #[test] + fn test_setuid_all_sub_invalid() { + // Configuration de test + let config = setup_test_config(1); // Un seul rôle pour simplifier + let role = setup_test_role(1, Some(config.as_ref().borrow().roles[0].clone()), None); + let task = role.as_ref().borrow().tasks[0].clone(); + + // Ajout d'un acteur autorisé + role.as_ref() + .borrow_mut() + .actors + .push(SActor::user("root").build()); + task.as_ref().borrow_mut().commands.default_behavior = Some(SetBehavior::All); + + // Définition du `setuid` avec un `fallback` + let fallback_user = SUserType::from(get_non_root_uid()); + let chooser_struct = SSetuidSet { + fallback: fallback_user.clone(), + default: SetBehavior::All, + add: vec![], + sub: vec![SUserType::from("root")], // Restriction d'un utilisateur + }; + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::ChooserStruct(chooser_struct)); + + // Création des credentials avec l'utilisateur correspondant à l'ajout + let cred = Cred { + user: User::from_name("root").unwrap().unwrap(), // Même nom que l'ajout + groups: vec![Group::from_name("root").unwrap().unwrap()], + ppid: Pid::from_raw(0), + tty: None, + }; + // Commande de test + let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; + // Exécution du match + let filter_matcher = FilterMatcher::builder().user("root").build(); + + let result = config.matches(&cred, &Some(filter_matcher), &command); + + // Vérification que le match est réussi + assert!(result.is_err()); + let result = result.unwrap_err(); + + // Vérification que l'erreur est bien de type `NoMatch` + assert!(result.is_no_match()); + + println!("Test réussi : L'utilisateur spécifié ne correspond pas "); + } + + #[test] + //echec + fn test_setuid_all_valid() { + // Configuration de test + let config = setup_test_config(1); // Un seul rôle pour simplifier + let role = setup_test_role(1, Some(config.as_ref().borrow().roles[0].clone()), None); + let task = role.as_ref().borrow().tasks[0].clone(); + + // Ajout d'un acteur autorisé + role.as_ref() + .borrow_mut() + .actors + .push(SActor::user("root").build()); + + task.as_ref().borrow_mut().commands.default_behavior = Some(SetBehavior::All); + + // Définition du `setuid` avec un `fallback` + let fallback_user = SUserType::from(get_non_root_uid()); + let chooser_struct = SSetuidSet { + fallback: fallback_user.clone(), + default: SetBehavior::All, + add: vec![], + sub: vec![], + }; + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::ChooserStruct(chooser_struct)); + + // Création des credentials avec l'utilisateur correspondant à l'ajout + let cred = Cred { + user: User::from_name("root").unwrap().unwrap(), // Même nom que l'ajout + groups: vec![Group::from_name("root").unwrap().unwrap()], + ppid: Pid::from_raw(0), + tty: None, + }; + + // Commande de test + let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; + // Exécution du match + let filter_matcher = FilterMatcher::builder().user("root").build(); + + let result = config.matches(&cred, &Some(filter_matcher), &command); + + // Vérification que le match est réussi + assert!(result.is_ok()); + let result = result.unwrap(); + + // Vérification que l'utilisateur assigné est bien celui de l'ajout + assert_eq!(result.settings.setuid, Some(SUserType::from("root"))); + + println!("Test réussi : L'utilisateur spécifié correspond bien à l'ajout."); + } + + #[test] + fn test_setuid_none_invalid() { + // Configuration de test + let config = setup_test_config(1); // Un seul rôle pour simplifier + let role = setup_test_role(1, Some(config.as_ref().borrow().roles[0].clone()), None); + let task = role.as_ref().borrow().tasks[0].clone(); + + // Ajout d'un acteur autorisé + role.as_ref() + .borrow_mut() + .actors + .push(SActor::user("root").build()); + + // Définition du `setuid` avec un `fallback` + let fallback_user = SUserType::from(get_non_root_uid()); + let chooser_struct = SSetuidSet { + fallback: fallback_user.clone(), + default: SetBehavior::None, + add: vec![], + sub: vec![], + }; + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::ChooserStruct(chooser_struct)); + + // Création des credentials avec l'utilisateur correspondant à l'ajout + let cred = Cred { + user: User::from_name("root").unwrap().unwrap(), // Même nom que l'ajout + groups: vec![Group::from_name("root").unwrap().unwrap()], + ppid: Pid::from_raw(0), + tty: None, + }; + + // Commande de test + let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; + // Exécution du match + let filter_matcher = FilterMatcher::builder().user("root").build(); + let result = config.matches(&cred, &Some(filter_matcher), &command); + + // Vérification que le match est réussi + assert!(result.is_err()); + let result = result.unwrap_err(); + + // Vérification que l'erreur est bien de type `NoMatch` + assert!(result.is_no_match()); + + println!("Test réussi : L'utilisateur spécifié ne correspond pas "); + } + + #[test] + fn test_setuid_all_add_valid() { + // Configuration de test + let config = setup_test_config(1); // Un seul rôle pour simplifier + let role = setup_test_role(1, Some(config.as_ref().borrow().roles[0].clone()), None); + let task = role.as_ref().borrow().tasks[0].clone(); + + task.as_ref().borrow_mut().commands.default_behavior = Some(SetBehavior::All); + + // Ajout d'un acteur autorisé + role.as_ref() + .borrow_mut() + .actors + .push(SActor::user("root").build()); + + task.as_ref().borrow_mut().commands.default_behavior = Some(SetBehavior::All); + + // Définition du `setuid` avec un `fallback` + let fallback_user = SUserType::from(get_non_root_uid()); + let chooser_struct = SSetuidSet { + fallback: fallback_user.clone(), + default: SetBehavior::All, + add: vec![SUserType::from("root")], // Ajout d'un utilisateur + sub: vec![], + }; + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::ChooserStruct(chooser_struct)); + + // Création des credentials avec l'utilisateur correspondant à l'ajout + let cred = Cred { + user: User::from_name("root").unwrap().unwrap(), // Même nom que l'ajout + groups: vec![Group::from_name("root").unwrap().unwrap()], + ppid: Pid::from_raw(0), + tty: None, + }; + + // Commande de test + let command = vec!["/bin/ls".to_string(), "-l".to_string(), "-a".to_string()]; + // Exécution du match + let filter_matcher = FilterMatcher::builder().user("root").build(); + + let result = config.matches(&cred, &Some(filter_matcher), &command); + + // Vérification que le match est réussi + assert!(result.is_ok()); + let result = result.unwrap(); + + // Vérification que l'utilisateur assigné est bien celui de l'ajout + assert_eq!(result.settings.setuid, Some(SUserType::from("root"))); + + println!("Test réussi : L'utilisateur spécifié correspond bien à l'ajout."); + } + #[test] fn test_equal_settings() { let mut settings1 = ExecSettings::new(); @@ -1385,7 +1999,7 @@ mod tests { role.as_ref() .borrow_mut() .actors - .push(SActor::from_user_string("root")); + .push(SActor::user("root").build()); let mut task1 = STask::default(); let mut task2 = STask::default(); task1.name = IdTask::Name("task1".to_string()); @@ -1434,7 +2048,7 @@ mod tests { let config = config.data; make_weak_config(&config); config.as_ref().borrow_mut()[0].as_ref().borrow_mut().actors[0] = - SActor::from_user_string("root"); + SActor::user("root").build(); let cred = Cred { user: User::from_name("root").unwrap().unwrap(), groups: vec![Group::from_name("root").unwrap().unwrap()], @@ -1462,4 +2076,110 @@ mod tests { IdTask::Name("t_chsr".to_string()) ); } + #[test] + + fn test_schooseruser_setuid_types() { + let config = SConfig::builder() + .role( + SRole::builder("test") + .actor(SActor::user("root").build()) + .task( + STask::builder(1) + .cred( + SCredentials::builder() + .setuid(SUserChooser::ChooserStruct( + SSetuidSet::builder(SUserType::from(0), SetBehavior::None) + .build(), + )) + .build(), + ) + .commands( + SCommands::builder(SetBehavior::None) + .add(["/bin/ls".into()]) + .build(), + ) + .build(), + ) + .task( + STask::builder(2) + .cred(SCredentials::builder().setuid("root").build()) + .commands( + SCommands::builder(SetBehavior::None) + .add(["/bin/pwd".into()]) + .build(), + ) + .build(), + ) + .task( + STask::builder(3) + .cred(SCredentials::builder().setuid(0).build()) + .commands( + SCommands::builder(SetBehavior::None) + .add(["/bin/cat".into()]) + .build(), + ) + .build(), + ) + .build(), + ) + .build(); + + // Vérifier si les tâches existent avant d’appeler unwrap() + + let t1 = config + .task("test", 1) + .expect(" Erreur : La tâche 1 n'existe pas !"); + let t2 = config + .task("test", 2) + .expect(" Erreur : La tâche 2 n'existe pas !"); + let t3 = config + .task("test", 3) + .expect(" Erreur : La tâche 3 n'existe pas !"); + + // Affichage pour debug + println!("Tâche 1 : {:?}", t1); + println!("Tâche 2 : {:?}", t2); + println!("Tâche 3 : {:?}", t3); + + let cred = Cred::builder().user_name("root").group_name("root").build(); + + let chooser_struct2 = SUserType::from("root"); + let chooser_struct3 = SUserType::from(0); + + let command1 = vec!["/bin/ls".to_string()]; + let command2 = vec!["/bin/pwd".to_string()]; + let command3 = vec!["/bin/cat".to_string()]; + + let filter_matcher = FilterMatcher::builder().user("root").build(); + let result = config.matches(&cred, &Some(filter_matcher), &command1); + assert!(result.is_ok(), "Erreur : L !"); + let result1 = config.matches(&cred, &None, &command1); + assert!(result1.is_ok(), "Erreur : La tâche 1 ne correspond pas !"); + let result2 = config.matches(&cred, &None, &command2); + assert!(result2.is_ok(), "Erreur : La tâche 2 ne correspond pas !"); + let result3 = config.matches(&cred, &None, &command3); + assert!(result3.is_ok(), "Erreur : La tâche 3 ne correspond pas !"); + + let result1 = result1.unwrap(); + let result2 = result2.unwrap(); + let result3 = result3.unwrap(); + + assert_eq!(result1.settings.setuid, Some(SUserType::from(0))); + assert_eq!(result1.settings.task.upgrade(), Some(t1.clone())); + println!( + " Test réussi : L'utilisateur spécifié correspond bien à l'ajout pour la tâche 1." + ); + + assert_eq!(result2.settings.setuid, Some(chooser_struct2)); + assert_eq!(result2.settings.task.upgrade(), Some(t2.clone())); + println!( + " Test réussi : L'utilisateur spécifié correspond bien à l'ajout pour la tâche 2." + ); + + assert_eq!(result3.settings.setuid, Some(chooser_struct3)); + assert_eq!(result3.settings.task.upgrade(), Some(t3.clone())); + println!( + " Test réussi : L'utilisateur spécifié correspond bien à l'ajout pour la tâche 3." + ); + } } diff --git a/rar-common/src/database/mod.rs b/rar-common/src/database/mod.rs index 0c22de9c..d68035f1 100644 --- a/rar-common/src/database/mod.rs +++ b/rar-common/src/database/mod.rs @@ -5,9 +5,12 @@ use crate::save_settings; use crate::util::{toggle_lock_config, ImmutableLock}; use crate::version::PACKAGE_VERSION; +use actor::SUserType; +use bon::{builder, Builder}; use chrono::Duration; use linked_hash_set::LinkedHashSet; use log::debug; +use options::EnvBehavior; use serde::{de, Deserialize, Serialize}; use self::{migration::Migration, options::EnvKey, structs::SConfig, versionning::Versioning}; @@ -17,13 +20,23 @@ use crate::SettingsFile; use crate::{open_with_privileges, write_json_config}; use crate::{util::immutable_effective, RemoteStorageSettings, ROOTASROLE}; +pub mod actor; #[cfg(feature = "finder")] pub mod finder; pub mod migration; pub mod options; pub mod structs; pub mod versionning; -pub mod wrapper; + +#[derive(Debug, Default, Builder)] +#[builder(on(_, overwritable))] +pub struct FilterMatcher { + pub role: Option, + pub task: Option, + pub env_behavior: Option, + #[builder(into)] + pub user: Option, +} pub fn make_weak_config(config: &Rc>) { for role in &config.as_ref().borrow().roles { diff --git a/rar-common/src/database/options.rs b/rar-common/src/database/options.rs index de34e22e..dc2ba294 100644 --- a/rar-common/src/database/options.rs +++ b/rar-common/src/database/options.rs @@ -24,8 +24,7 @@ use crate::rc_refcell; #[cfg(feature = "finder")] use super::finder::Cred; -use super::finder::FilterMatcher; -use super::{deserialize_duration, is_default, serialize_duration}; +use super::{FilterMatcher, deserialize_duration, is_default, serialize_duration}; use super::{ lhs_deserialize, lhs_deserialize_envkey, lhs_serialize, lhs_serialize_envkey, @@ -34,8 +33,8 @@ use super::{ #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub enum Level { - None, #[default] + None, Default, Global, Role, @@ -154,9 +153,8 @@ pub struct SEnvOptions { #[serde(rename = "default", default, skip_serializing_if = "is_default")] #[builder(start_fn)] pub default_behavior: EnvBehavior, - #[serde(alias = "override", default, skip_serializing_if = "is_default")] - #[builder(default)] - pub override_behavior: bool, + #[serde(alias = "override", default, skip_serializing_if = "Option::is_none")] + pub override_behavior: Option, #[serde(default, skip_serializing_if = "HashMap::is_empty")] #[builder(default, with = |iter: impl IntoIterator| { let mut map = HashMap::with_hasher(Default::default()); @@ -170,7 +168,7 @@ pub struct SEnvOptions { deserialize_with = "lhs_deserialize_envkey", serialize_with = "lhs_serialize_envkey" )] - #[builder(default, with = |v : impl IntoIterator| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string()).map_err(|e| e)?); } Ok(res)})] + #[builder(default, with = |v : impl IntoIterator| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string())?); } Ok(res)})] pub keep: LinkedHashSet, #[serde( default, @@ -178,7 +176,7 @@ pub struct SEnvOptions { deserialize_with = "lhs_deserialize_envkey", serialize_with = "lhs_serialize_envkey" )] - #[builder(default, with = |v : impl IntoIterator| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string()).map_err(|e| e)?); } Ok(res)})] + #[builder(default, with = |v : impl IntoIterator| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string())?); } Ok(res)})] pub check: LinkedHashSet, #[serde( default, @@ -186,7 +184,7 @@ pub struct SEnvOptions { deserialize_with = "lhs_deserialize_envkey", serialize_with = "lhs_serialize_envkey" )] - #[builder(default, with = |v : impl IntoIterator| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string()).map_err(|e| e)?); } Ok(res)})] + #[builder(default, with = |v : impl IntoIterator| -> Result<_,String> { let mut res = LinkedHashSet::new(); for s in v { res.insert(EnvKey::new(s.to_string())?); } Ok(res)})] pub delete: LinkedHashSet, #[serde(default, flatten)] #[builder(default)] @@ -312,7 +310,7 @@ impl Opt { "PS2", "XAUTHORY", "XAUTHORIZATION", - "XDG_CURRENT_DESKTOP".into(), + "XDG_CURRENT_DESKTOP", ]) .unwrap() .check([ @@ -584,7 +582,7 @@ impl OptStackBuilder { } self } - fn from_task( + fn with_task( self, task: Rc>, ) -> OptStackBuilder< @@ -595,7 +593,7 @@ impl OptStackBuilder { ::Role: opt_stack_builder::IsUnset, ::Task: opt_stack_builder::IsUnset, { - self.from_role( + self.with_role( task.as_ref() .borrow() ._role @@ -607,7 +605,7 @@ impl OptStackBuilder { .task(task.to_owned()) .opt(task.as_ref().borrow().options.to_owned()) } - fn from_role( + fn with_role( self, role: Rc>, ) -> OptStackBuilder>> @@ -615,7 +613,7 @@ impl OptStackBuilder { ::Roles: opt_stack_builder::IsUnset, ::Role: opt_stack_builder::IsUnset, { - self.from_roles( + self.with_roles( role.as_ref() .borrow() ._config @@ -628,16 +626,83 @@ impl OptStackBuilder { .opt(role.as_ref().borrow().options.to_owned()) } - fn from_roles( + fn with_roles( self, roles: Rc>, ) -> OptStackBuilder> where ::Roles: opt_stack_builder::IsUnset, { - self.roles(roles.to_owned()) + self.with_default().roles(roles.to_owned()) .opt(roles.as_ref().borrow().options.to_owned()) } + + fn with_default(self) -> Self { + self.opt(Some(Opt::builder(Level::Default) + .root(SPrivileged::User) + .bounding(SBounding::Strict) + .path( + SPathOptions::builder(PathBehavior::Delete) + .add([ + "/usr/local/sbin", + "/usr/local/bin", + "/usr/sbin", + "/usr/bin", + "/sbin", + "/bin", + "/snap/bin", + ]) + .build(), + ) + .authentication(SAuthentication::Perform) + .env( + SEnvOptions::builder(EnvBehavior::Delete) + .keep([ + "HOME", + "USER", + "LOGNAME", + "COLORS", + "DISPLAY", + "HOSTNAME", + "KRB5CCNAME", + "LS_COLORS", + "PS1", + "PS2", + "XAUTHORY", + "XAUTHORIZATION", + "XDG_CURRENT_DESKTOP", + ]) + .unwrap() + .check([ + "COLORTERM", + "LANG", + "LANGUAGE", + "LC_*", + "LINGUAS", + "TERM", + "TZ", + ]) + .unwrap() + .delete([ + "PS4", + "SHELLOPTS", + "PERLLIB", + "PERL5LIB", + "PERL5OPT", + "PYTHONINSPECT", + ]) + .unwrap() + .build(), + ) + .timeout( + STimeout::builder() + .type_field(TimestampType::TTY) + .duration(Duration::minutes(5)) + .build(), + ) + .wildcard_denied(";&|") + .build())) + } } #[bon] @@ -649,22 +714,21 @@ impl OptStack { role: Option>>, task: Option>>, ) -> Self { - let v = OptStack { + OptStack { stack, roles, role, task, - }; - v + } } pub fn from_task(task: Rc>) -> Self { - OptStack::builder().from_task(task).build() + OptStack::builder().with_task(task).build() } pub fn from_role(role: Rc>) -> Self { - OptStack::builder().from_role(role).build() + OptStack::builder().with_role(role).build() } pub fn from_roles(roles: Rc>) -> Self { - OptStack::builder().from_roles(roles).build() + OptStack::builder().with_roles(roles).build() } fn find_in_options Option<(Level, V)>, V>(&self, f: F) -> Option<(Level, V)> { @@ -939,7 +1003,7 @@ impl OptStack { let mut final_behavior = cmd_filter .as_ref() .and_then(|f| f.env_behavior) - .unwrap_or(EnvBehavior::default()); + .unwrap_or_default(); let mut final_set = HashMap::new(); let mut final_keep = LinkedHashSet::new(); let mut final_check = LinkedHashSet::new(); diff --git a/rar-common/src/database/structs.rs b/rar-common/src/database/structs.rs index ba167326..2e1de1b1 100644 --- a/rar-common/src/database/structs.rs +++ b/rar-common/src/database/structs.rs @@ -1,10 +1,6 @@ use bon::{bon, builder, Builder}; use capctl::{Cap, CapSet}; use derivative::Derivative; -use nix::{ - errno::Errno, - unistd::{Group, User}, -}; use serde::{ de::{self, MapAccess, SeqAccess, Visitor}, ser::SerializeMap, @@ -15,7 +11,6 @@ use strum::{Display, EnumIs}; use std::{ cell::RefCell, - cmp::Ordering, error::Error, fmt, ops::{Index, Not}, @@ -23,15 +18,19 @@ use std::{ }; use super::{ + actor::{SActor, SGroups, SUserType}, is_default, options::{Level, Opt, OptBuilder}, - wrapper::{OptWrapper, STaskWrapper}, }; #[derive(Deserialize, Serialize, PartialEq, Eq, Debug)] pub struct SConfig { - #[serde(skip_serializing_if = "Option::is_none")] - pub options: OptWrapper, + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "sconfig_opt" + )] + pub options: Option>>, #[serde(default, skip_serializing_if = "Vec::is_empty")] pub roles: Vec>>, #[serde(default)] @@ -39,6 +38,15 @@ pub struct SConfig { pub _extra_fields: Map, } +fn sconfig_opt<'de, D>(deserializer: D) -> Result>>, D::Error> +where + D: Deserializer<'de>, +{ + let mut opt = Opt::deserialize(deserializer)?; + opt.level = Level::Global; + Ok(Some(Rc::new(RefCell::new(opt)))) +} + #[derive(Serialize, Deserialize, Debug, Derivative)] #[serde(rename_all = "kebab-case")] #[derivative(PartialEq, Eq)] @@ -47,9 +55,13 @@ pub struct SRole { #[serde(default, skip_serializing_if = "Vec::is_empty")] pub actors: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] - pub tasks: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub options: OptWrapper, + pub tasks: Vec>>, + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "srole_opt" + )] + pub options: Option>>, #[serde(default, flatten, skip_serializing_if = "Map::is_empty")] pub _extra_fields: Map, #[serde(skip)] @@ -57,60 +69,13 @@ pub struct SRole { pub _config: Option>>, } -#[derive(Serialize, PartialEq, Eq, Debug, EnumIs, Clone)] -#[serde(untagged, rename_all = "lowercase")] -pub enum SActorType { - Id(u32), - Name(String), -} - -impl std::fmt::Display for SActorType { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SActorType::Id(id) => write!(f, "{}", id), - SActorType::Name(name) => write!(f, "{}", name), - } - } -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone, EnumIs)] -#[serde(untagged)] -pub enum SGroups { - Single(SActorType), - Multiple(Vec), -} - -impl SGroups { - pub fn len(&self) -> usize { - match self { - SGroups::Single(_) => 1, - SGroups::Multiple(groups) => groups.len(), - } - } - pub fn is_empty(&self) -> bool { - self.len() == 0 - } -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs)] -#[serde(tag = "type", rename_all = "lowercase")] -pub enum SActor { - #[serde(rename = "user")] - User { - #[serde(alias = "name", skip_serializing_if = "Option::is_none")] - id: Option, - #[serde(default, flatten, skip_serializing_if = "Map::is_empty")] - _extra_fields: Map, - }, - #[serde(rename = "group")] - Group { - #[serde(alias = "names", skip_serializing_if = "Option::is_none")] - groups: Option, - #[serde(default, flatten)] - _extra_fields: Map, - }, - #[serde(untagged)] - Unknown(Value), +fn srole_opt<'de, D>(deserializer: D) -> Result>>, D::Error> +where + D: Deserializer<'de>, +{ + let mut opt = Opt::deserialize(deserializer)?; + opt.level = Level::Role; + Ok(Some(Rc::new(RefCell::new(opt)))) } #[derive(Serialize, Deserialize, PartialEq, Eq, Debug, EnumIs)] @@ -140,8 +105,12 @@ pub struct STask { pub cred: SCredentials, #[serde(default, skip_serializing_if = "is_default")] pub commands: SCommands, - #[serde(default, skip_serializing_if = "Option::is_none")] - pub options: OptWrapper, + #[serde( + default, + skip_serializing_if = "Option::is_none", + deserialize_with = "stask_opt" + )] + pub options: Option>>, #[serde(default, flatten, skip_serializing_if = "Map::is_empty")] pub _extra_fields: Map, #[serde(skip)] @@ -149,12 +118,21 @@ pub struct STask { pub _role: Option>>, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Builder)] +fn stask_opt<'de, D>(deserializer: D) -> Result>>, D::Error> +where + D: Deserializer<'de>, +{ + let mut opt = Opt::deserialize(deserializer)?; + opt.level = Level::Task; + Ok(Some(Rc::new(RefCell::new(opt)))) +} + +#[derive(Serialize, Deserialize, Debug, Builder, PartialEq, Eq)] #[serde(rename_all = "kebab-case")] pub struct SCredentials { #[serde(skip_serializing_if = "Option::is_none")] #[builder(into)] - pub setuid: Option, + pub setuid: Option, #[serde(skip_serializing_if = "Option::is_none")] #[builder(into)] pub setgid: Option, @@ -168,7 +146,54 @@ pub struct SCredentials { pub _extra_fields: Map, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Display, Debug, EnumIs)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[serde(untagged)] +pub enum SUserChooser { + Actor(SUserType), + ChooserStruct(SSetuidSet), +} + +impl From for SUserChooser { + fn from(actor: SUserType) -> Self { + SUserChooser::Actor(actor) + } +} + +impl From for SUserChooser { + fn from(set: SSetuidSet) -> Self { + SUserChooser::ChooserStruct(set) + } +} + +impl From<&str> for SUserChooser { + fn from(name: &str) -> Self { + SUserChooser::Actor(name.into()) + } +} + +impl From for SUserChooser { + fn from(id: u32) -> Self { + SUserChooser::Actor(id.into()) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, Builder, PartialEq, Eq)] + +pub struct SSetuidSet { + #[builder(start_fn, into)] + pub fallback: SUserType, + #[serde(rename = "default", default, skip_serializing_if = "is_default")] + #[builder(start_fn)] + pub default: SetBehavior, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[builder(default, with = FromIterator::from_iter)] + pub add: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] + #[builder(default, with = FromIterator::from_iter)] + pub sub: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Display, Debug, EnumIs, Clone)] #[serde(rename_all = "lowercase")] #[derive(Default)] pub enum SetBehavior { @@ -190,7 +215,7 @@ pub struct SCapabilities { } impl SCapabilitiesBuilder { - pub fn add(mut self, cap: Cap) -> Self { + pub fn add_cap(mut self, cap: Cap) -> Self { self.add.add(cap); self } @@ -198,7 +223,7 @@ impl SCapabilitiesBuilder { self.add = set; self } - pub fn sub(mut self, cap: Cap) -> Self { + pub fn sub_cap(mut self, cap: Cap) -> Self { self.sub.add(cap); self } @@ -287,7 +312,7 @@ impl<'de> Deserialize<'de> for SCapabilities { map.next_value().expect("add entry must be a list"); for value in values { add.add(value.parse().map_err(|_| { - de::Error::custom(&format!("Invalid capability: {}", value)) + de::Error::custom(format!("Invalid capability: {}", value)) })?); } } @@ -296,7 +321,7 @@ impl<'de> Deserialize<'de> for SCapabilities { map.next_value().expect("sub entry must be a list"); for value in values { sub.add(value.parse().map_err(|_| { - de::Error::custom(&format!("Invalid capability: {}", value)) + de::Error::custom(format!("Invalid capability: {}", value)) })?); } } @@ -413,6 +438,12 @@ impl Default for SCapabilities { } } +impl Default for SSetuidSet { + fn default() -> Self { + SSetuidSet::builder(0, SetBehavior::None).build() + } +} + impl Default for IdTask { fn default() -> Self { IdTask::Number(0) @@ -441,54 +472,6 @@ impl From<&str> for IdTask { } } -impl From for SActorType { - fn from(id: u32) -> Self { - SActorType::Id(id) - } -} - -impl From for SActorType { - fn from(name: String) -> Self { - SActorType::Name(name) - } -} - -impl From<&str> for SActorType { - fn from(name: &str) -> Self { - SActorType::Name(name.to_string()) - } -} - -impl From> for SGroups { - fn from(groups: Vec) -> Self { - if groups.len() == 1 { - SGroups::Single(groups[0].clone().into()) - } else { - SGroups::Multiple(groups.into_iter().map(|x| x.into()).collect()) - } - } -} - -impl From<[&str; N]> for SGroups { - fn from(groups: [&str; N]) -> Self { - if N == 1 { - SGroups::Single(groups[0].to_string().into()) - } else { - SGroups::Multiple(groups.iter().map(|&x| x.into()).collect()) - } - } -} - -impl From> for SGroups { - fn from(groups: Vec) -> Self { - if groups.len() == 1 { - SGroups::Single(groups[0].into()) - } else { - SGroups::Multiple(groups.into_iter().map(|x| x.into()).collect()) - } - } -} - impl From<&str> for SCommand { fn from(name: &str) -> Self { SCommand::Simple(name.to_string()) @@ -509,42 +492,6 @@ impl From for SCapabilities { // ------------------------ // This try to deserialize a number as an ID and a string as a name -impl<'de> Deserialize<'de> for SActorType { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct IdVisitor; - - impl<'de> Visitor<'de> for IdVisitor { - type Value = SActorType; - - fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("user ID as a number or string") - } - - fn visit_u32(self, id: u32) -> Result - where - E: de::Error, - { - Ok(SActorType::Id(id)) - } - - fn visit_str(self, id: &str) -> Result - where - E: de::Error, - { - let rid: Result = id.parse(); - match rid { - Ok(id) => Ok(SActorType::Id(id)), - Err(_) => Ok(SActorType::Name(id.to_string())), - } - } - } - - deserializer.deserialize_any(IdVisitor) - } -} // ======================== // Implementations for Struct navigation @@ -620,6 +567,10 @@ impl SConfigBuilder { self.roles.push(role); self } + pub fn roles(mut self, roles: impl IntoIterator>>) -> Self { + self.roles.extend(roles); + self + } } impl SRoleBuilder { @@ -711,53 +662,13 @@ impl Index for SRole { } } -// ================= -// Display implementations -// ================= - -impl core::fmt::Display for SActor { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SActor::User { id, _extra_fields } => { - write!(f, "User: {}", id.as_ref().unwrap()) - } - SActor::Group { - groups, - _extra_fields, - } => { - write!(f, "Group: {}", groups.as_ref().unwrap()) - } - SActor::Unknown(unknown) => { - write!(f, "Unknown: {}", unknown) - } - } - } -} - -impl core::fmt::Display for SGroups { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - SGroups::Single(group) => { - write!(f, "{}", group) - } - SGroups::Multiple(groups) => { - write!(f, "{:?}", groups) - } - } - } -} - -// ================= -// Other implementations -// ================= - #[bon] impl SCommands { #[builder] pub fn new( #[builder(start_fn)] default_behavior: SetBehavior, - #[builder(with = FromIterator::from_iter)] add: Vec, - #[builder(with = FromIterator::from_iter)] sub: Vec, + #[builder(default, with = FromIterator::from_iter)] add: Vec, + #[builder(default, with = FromIterator::from_iter)] sub: Vec, #[builder(default, with = <_>::from_iter)] _extra_fields: Map, ) -> Self { SCommands { @@ -781,196 +692,11 @@ impl SCapabilities { } } -impl PartialEq for SActorType { - fn eq(&self, other: &str) -> bool { - match self { - SActorType::Name(name) => name == other, - SActorType::Id(id) => other.parse().map(|oid: u32| oid == *id).unwrap_or(false), - } - } -} - -impl PartialEq for SActorType { - fn eq(&self, other: &User) -> bool { - match self { - SActorType::Name(name) => name == &other.name, - SActorType::Id(id) => other.uid.as_raw() == *id, - } - } -} - -impl PartialEq for SActorType { - fn eq(&self, other: &Group) -> bool { - match self { - SActorType::Name(name) => name == &other.name, - SActorType::Id(id) => other.gid.as_raw() == *id, - } - } -} - -impl PartialEq for SGroups { +impl PartialEq for SUserChooser { fn eq(&self, other: &str) -> bool { match self { - SGroups::Single(actor) => actor == other, - SGroups::Multiple(actors) => actors.len() == 1 && &actors[0] == other, - } - } -} - -impl PartialEq> for SGroups { - fn eq(&self, other: &Vec) -> bool { - match self { - SGroups::Single(actor) => { - if other.len() == 1 { - return actor == &other[0]; - } - } - SGroups::Multiple(actors) => { - if actors.len() == other.len() { - return actors.iter().all(|actor| other.iter().any(|x| actor == x)); - } - } - } - false - } -} - -impl FromIterator for SGroups { - fn from_iter>(iter: I) -> Self { - let mut iter = iter.into_iter(); - let first = iter.next().unwrap(); - let mut groups = vec![SActorType::Name(first)]; - for group in iter { - groups.push(SActorType::Name(group)); - } - if groups.len() == 1 { - SGroups::Single(groups[0].to_owned()) - } else { - SGroups::Multiple(groups) - } - } -} - -impl From for Vec { - fn from(val: SGroups) -> Self { - match val { - SGroups::Single(group) => vec![group], - SGroups::Multiple(groups) => groups, - } - } -} - -impl SActorType { - pub fn into_group(&self) -> Result, Errno> { - match self { - SActorType::Name(name) => Group::from_name(name), - SActorType::Id(id) => Group::from_gid(id.to_owned().into()), - } - } - pub fn into_user(&self) -> Result, Errno> { - match self { - SActorType::Name(name) => User::from_name(name), - SActorType::Id(id) => User::from_uid(id.to_owned().into()), - } - } -} - -impl PartialOrd for SGroups { - fn partial_cmp(&self, other: &SGroups) -> Option { - let other = Into::>::into(other.clone()); - self.partial_cmp(&other) - } -} - -impl PartialOrd> for SGroups { - fn partial_cmp(&self, other: &Vec) -> Option { - match self { - SGroups::Single(group) => { - if other.len() == 1 { - if group == &other[0] { - return Some(Ordering::Equal); - } - } else if other.iter().any(|x| group == x) { - return Some(Ordering::Less); - } - } - SGroups::Multiple(groups) => { - if groups.is_empty() && other.is_empty() { - return Some(Ordering::Equal); - } else if groups.len() == other.len() { - if groups.iter().all(|x| other.iter().any(|y| x == y)) { - return Some(Ordering::Equal); - } - } else if groups.len() < other.len() { - if groups.iter().all(|x| other.iter().any(|y| x == y)) { - return Some(Ordering::Less); - } - } else if other.iter().all(|x| groups.iter().any(|y| y == x)) { - return Some(Ordering::Greater); - } - } - } - None - } -} - -impl From for Vec { - fn from(val: SGroups) -> Self { - match val { - SGroups::Single(group) => vec![group.into_group().unwrap().unwrap()], - SGroups::Multiple(groups) => groups - .into_iter() - .map(|x| x.into_group().unwrap().unwrap()) - .collect(), - } - } -} - -impl SActor { - pub fn from_user_string(user: &str) -> Self { - SActor::User { - id: Some(user.into()), - _extra_fields: Map::default(), - } - } - pub fn from_user_id(id: u32) -> Self { - SActor::User { - id: Some(id.into()), - _extra_fields: Map::default(), - } - } - pub fn from_group_string(group: &str) -> Self { - SActor::Group { - groups: Some(SGroups::Single(group.into())), - _extra_fields: Map::default(), - } - } - pub fn from_group_id(id: u32) -> Self { - SActor::Group { - groups: Some(SGroups::Single(id.into())), - _extra_fields: Map::default(), - } - } - pub fn from_group_vec_string(group: Vec<&str>) -> Self { - Self::from_group_vec_actors( - group - .into_iter() - .map(|str| str.into()) - .collect::>(), - ) - } - pub fn from_group_vec_id(groups: Vec) -> Self { - Self::from_group_vec_actors( - groups - .into_iter() - .map(|id| id.into()) - .collect::>(), - ) - } - pub fn from_group_vec_actors(groups: Vec) -> Self { - SActor::Group { - groups: Some(SGroups::Multiple(groups)), - _extra_fields: Map::default(), + SUserChooser::Actor(actor) => actor == &SUserType::from(other), + SUserChooser::ChooserStruct(chooser) => chooser.fallback == *other, } } } @@ -983,7 +709,10 @@ mod tests { use crate::{ as_borrow, - database::options::{EnvBehavior, PathBehavior, SAuthentication, TimestampType}, + database::{ + actor::SGroupType, + options::{EnvBehavior, PathBehavior, SAuthentication, SBounding, SEnvOptions, SPathOptions, SPrivileged, STimeout, TimestampType}, + }, }; use super::*; @@ -993,7 +722,6 @@ mod tests { println!("START"); let config = r#" { - "version": "1.0.0", "options": { "path": { "default": "delete", @@ -1002,6 +730,7 @@ mod tests { }, "env": { "default": "delete", + "override_behavior": true, "keep": ["keep_env"], "check": ["check_env"] }, @@ -1032,7 +761,12 @@ mod tests { "name": "task1", "purpose": "purpose1", "cred": { - "setuid": "setuid1", + "setuid": { + "fallback": "user1", + "default": "all", + "add": ["user2"], + "sub": ["user3"] + }, "setgid": "setgid1", "capabilities": { "default": "all", @@ -1059,6 +793,7 @@ mod tests { assert!(path.add.front().is_some_and(|s| s == "path_add")); let env = options.env.as_ref().unwrap(); assert_eq!(env.default_behavior, EnvBehavior::Delete); + assert!(env.override_behavior.is_some_and(|b| b)); assert!(env.keep.front().is_some_and(|s| s == "keep_env")); assert!(env.check.front().is_some_and(|s| s == "check_env")); assert!(options.root.as_ref().unwrap().is_privileged()); @@ -1071,18 +806,19 @@ mod tests { assert_eq!(timeout.duration, Some(Duration::minutes(5))); assert_eq!(config.roles[0].as_ref().borrow().name, "role1"); let actor0 = &config.roles[0].as_ref().borrow().actors[0]; - match actor0 { - SActor::User { id, .. } => { - assert_eq!(id.as_ref().unwrap(), "user1"); + assert_eq!( + actor0, + &SActor::User { + id: Some("user1".into()), + _extra_fields: Map::default() } - _ => panic!("unexpected actor type"), - } + ); let actor1 = &config.roles[0].as_ref().borrow().actors[1]; match actor1 { SActor::Group { groups, .. } => match groups.as_ref().unwrap() { SGroups::Multiple(groups) => { - assert_eq!(groups[0], SActorType::Name("group1".into())); - assert_eq!(groups[1], SActorType::Id(1000)); + assert_eq!(&groups[0], "group1"); + assert_eq!(groups[1], 1000); } _ => panic!("unexpected actor group type"), }, @@ -1091,8 +827,16 @@ mod tests { let role = config.roles[0].as_ref().borrow(); assert_eq!(as_borrow!(role[0]).purpose.as_ref().unwrap(), "purpose1"); let cred = &as_borrow!(&role[0]).cred; - assert_eq!(cred.setuid.as_ref().unwrap(), "setuid1"); - assert_eq!(cred.setgid.as_ref().unwrap(), "setgid1"); + let setuidstruct = SSetuidSet { + fallback: "user1".into(), + default: SetBehavior::All, + add: ["user2".into()].into(), + sub: ["user3".into()].into(), + }; + assert!( + matches!(cred.setuid.as_ref().unwrap(), SUserChooser::ChooserStruct(set) if set == &setuidstruct) + ); + assert_eq!(*cred.setgid.as_ref().unwrap(), ["setgid1".into()]); let capabilities = cred.capabilities.as_ref().unwrap(); assert_eq!(capabilities.default_behavior, SetBehavior::All); assert!(capabilities.add.has(Cap::NET_BIND_SERVICE)); @@ -1109,7 +853,6 @@ mod tests { fn test_unknown_fields() { let config = r#" { - "version": "1.0.0", "options": { "path": { "default": "delete", @@ -1235,7 +978,6 @@ mod tests { fn test_deserialize_alias() { let config = r#" { - "version": "1.0.0", "options": { "path": { "default": "delete", @@ -1318,8 +1060,8 @@ mod tests { match actor1 { SActor::Group { groups, .. } => match groups.as_ref().unwrap() { SGroups::Multiple(groups) => { - assert_eq!(groups[0], SActorType::Name("group1".into())); - assert_eq!(groups[1], SActorType::Id(1000)); + assert_eq!(groups[0], SGroupType::from("group1")); + assert_eq!(groups[1], SGroupType::from(1000)); } _ => panic!("unexpected actor group type"), }, @@ -1328,8 +1070,11 @@ mod tests { let role = config.roles[0].as_ref().borrow(); assert_eq!(as_borrow!(role[0]).purpose.as_ref().unwrap(), "purpose1"); let cred = &as_borrow!(&role[0]).cred; - assert_eq!(cred.setuid.as_ref().unwrap(), "setuid1"); - assert_eq!(cred.setgid.as_ref().unwrap(), "setgid1"); + assert_eq!( + cred.setuid.as_ref().unwrap(), + &SUserChooser::from(SUserType::from("setuid1")) + ); + assert_eq!(cred.setgid.as_ref().unwrap(), &SGroups::from(["setgid1"])); let capabilities = cred.capabilities.as_ref().unwrap(); assert_eq!(capabilities.default_behavior, SetBehavior::None); assert!(capabilities.add.has(Cap::NET_BIND_SERVICE)); @@ -1344,20 +1089,60 @@ mod tests { } #[test] - fn test_sgroups_compare() { - let single = SGroups::Single(SActorType::Name("single".into())); - let multiple = SGroups::Multiple(vec![ - SActorType::Name("single".into()), - SActorType::Id(1000), - ]); - assert!(single == single); - assert!(single <= multiple); - assert!(multiple >= single); - assert!(multiple == multiple); - let multiple2 = SGroups::Multiple(vec![ - SActorType::Name("single".into()), - SActorType::Id(1001), - ]); - assert!(multiple != multiple2); + fn test_serialize() { + let config = SConfig::builder().role( + SRole::builder("role1") + .actor(SActor::user("user1").build()) + .actor(SActor::group([SGroupType::from("group1"), SGroupType::from(1000)]).build()) + .task( + STask::builder("task1") + .purpose("purpose1".into()) + .cred( + SCredentials::builder() + .setuid(SUserChooser::ChooserStruct( + SSetuidSet::builder("user1", SetBehavior::All) + .add(["user2".into()]) + .sub(["user3".into()]) + .build(), + )) + .setgid(["setgid1"]) + .capabilities( + SCapabilities::builder(SetBehavior::All) + .add_cap(Cap::NET_BIND_SERVICE) + .sub_cap(Cap::SYS_ADMIN) + .build(), + ) + .build(), + ) + .commands( + SCommands::builder(SetBehavior::All) + .add(["cmd1".into()]) + .sub(["cmd2".into()]) + .build(), + ) + .build(), + ) + .build(), + ).options( | opt | opt.path(SPathOptions::builder(PathBehavior::Delete).add(["path_add"]).sub(["path_sub"]).build()) + .env(SEnvOptions::builder(EnvBehavior::Delete).override_behavior(true).keep(["keep_env"]).unwrap().check(["check_env"]).unwrap().build()) + .root(SPrivileged::Privileged) + .bounding(SBounding::Ignore) + .authentication(SAuthentication::Skip) + .wildcard_denied("wildcards") + .timeout(STimeout::builder().type_field(TimestampType::PPID).duration(Duration::minutes(5)).build()) + .build() + ).build(); + let config = serde_json::to_string_pretty(&config).unwrap(); + println!("{}", config); + } + + + #[test] + fn test_serialize_operride_behavior_option() { + let config = SConfig::builder().options( | opt | opt.env(SEnvOptions::builder(EnvBehavior::Inherit).override_behavior(true).build()) + .build() + ).build(); + let config = serde_json::to_string(&config).unwrap(); + assert_eq!(config,"{\"options\":{\"env\":{\"override_behavior\":true}}}"); } } diff --git a/rar-common/src/database/wrapper.rs b/rar-common/src/database/wrapper.rs deleted file mode 100644 index 78b0a865..00000000 --- a/rar-common/src/database/wrapper.rs +++ /dev/null @@ -1,40 +0,0 @@ -use std::{cell::RefCell, rc::Rc}; - -use super::{ - options::Opt, - structs::{SConfig, SRole, STask}, -}; -pub type RcRefCell = Rc>; - -pub type SConfigWrapper = RcRefCell; -pub type SRoleWrapper = RcRefCell; -pub type STaskWrapper = RcRefCell; -pub type OptWrapper = Option>; - -pub trait DefaultWrapper { - fn default() -> Self; -} - -impl DefaultWrapper for SConfigWrapper { - fn default() -> Self { - Rc::new(RefCell::new(SConfig::default())) - } -} - -impl DefaultWrapper for SRoleWrapper { - fn default() -> Self { - Rc::new(RefCell::new(SRole::default())) - } -} - -impl DefaultWrapper for STaskWrapper { - fn default() -> Self { - Rc::new(RefCell::new(STask::default())) - } -} - -impl DefaultWrapper for OptWrapper { - fn default() -> Self { - None - } -} diff --git a/rar-common/src/lib.rs b/rar-common/src/lib.rs index 856aad2e..896f2891 100644 --- a/rar-common/src/lib.rs +++ b/rar-common/src/lib.rs @@ -109,6 +109,7 @@ pub struct Settings { } #[derive(Serialize, Deserialize, Debug, Clone, Builder)] +#[derive(Default)] pub struct RemoteStorageSettings { #[serde(skip_serializing_if = "Option::is_none")] #[builder(name = not_immutable,with = || false)] @@ -193,21 +194,7 @@ impl Default for Settings { } } -impl Default for RemoteStorageSettings { - fn default() -> Self { - Self { - immutable: None, - path: None, - host: None, - port: None, - auth: None, - database: None, - schema: None, - table_prefix: None, - properties: None, - } - } -} + pub fn save_settings(settings: Rc>) -> Result<(), Box> { let default_remote: RemoteStorageSettings = RemoteStorageSettings::default(); diff --git a/rar-common/src/plugin/hashchecker.rs b/rar-common/src/plugin/hashchecker.rs index 05ae1887..3babc952 100644 --- a/rar-common/src/plugin/hashchecker.rs +++ b/rar-common/src/plugin/hashchecker.rs @@ -127,9 +127,10 @@ mod tests { use super::*; + use crate::database::actor::SActor; use crate::database::finder::{Cred, TaskMatcher}; use crate::{ - database::structs::{IdTask, SActor, SCommand, SCommands, SConfig, SRole, STask}, + database::structs::{IdTask, SCommand, SCommands, SConfig, SRole, STask}, rc_refcell, }; @@ -160,7 +161,7 @@ mod tests { .as_ref() .borrow_mut() .actors - .push(SActor::from_user_id(0)); + .push(SActor::user(0).build()); config.as_ref().borrow_mut().roles.push(role1); diff --git a/rar-common/src/plugin/hierarchy.rs b/rar-common/src/plugin/hierarchy.rs index d871b61d..91ffc4cc 100644 --- a/rar-common/src/plugin/hierarchy.rs +++ b/rar-common/src/plugin/hierarchy.rs @@ -3,8 +3,9 @@ use std::cmp::Ordering; use crate::{ api::{PluginManager, PluginResultAction}, database::{ - finder::{Cred, FilterMatcher, TaskMatch, TaskMatcher}, + finder::{Cred, TaskMatch, TaskMatcher}, structs::{RoleGetter, SRole}, + FilterMatcher, }, }; @@ -82,8 +83,9 @@ mod tests { use super::*; use crate::{ database::{ - finder::UserMin, - structs::{IdTask, SActor, SCommand, SCommands, SConfig, STask}, + actor::SActor, + finder::ActorMatchMin, + structs::{IdTask, SCommand, SCommands, SConfig, STask}, }, rc_refcell, }; @@ -112,7 +114,7 @@ mod tests { .as_ref() .borrow_mut() .actors - .push(SActor::from_user_id(0)); + .push(SActor::user(0).build()); role1.as_ref().borrow_mut()._extra_fields.insert( "parents".to_string(), serde_json::Value::Array(vec![serde_json::Value::String("role1".to_string())]), @@ -128,7 +130,7 @@ mod tests { tty: None, }; let mut matcher = TaskMatch::default(); - matcher.score.user_min = UserMin::UserMatch; + matcher.score.user_min = ActorMatchMin::UserMatch; let res = find_in_parents( &config.as_ref().borrow().roles[1].as_ref().borrow(), &cred, @@ -164,7 +166,7 @@ mod tests { .as_ref() .borrow_mut() .actors - .push(SActor::from_user_id(0)); + .push(SActor::user(0).build()); role1.as_ref().borrow_mut()._extra_fields.insert( "parents".to_string(), serde_json::Value::Array(vec![serde_json::Value::String("role1".to_string())]), @@ -180,7 +182,7 @@ mod tests { tty: None, }; let mut matcher = TaskMatch::default(); - matcher.score.user_min = UserMin::UserMatch; + matcher.score.user_min = ActorMatchMin::UserMatch; let matches = config.matches(&cred, &None, &["ls".to_string()]).unwrap(); assert_eq!( matches.settings.task.upgrade().unwrap(), diff --git a/rar-common/src/plugin/ssd.rs b/rar-common/src/plugin/ssd.rs index 2adbc98f..9a27f266 100644 --- a/rar-common/src/plugin/ssd.rs +++ b/rar-common/src/plugin/ssd.rs @@ -8,8 +8,9 @@ use crate::{ api::{PluginManager, PluginResult}, as_borrow, database::{ + actor::{SActor, SGroups}, finder::Cred, - structs::{RoleGetter, SActor, SConfig, SGroups, SRole}, + structs::{RoleGetter, SConfig, SRole}, }, }; @@ -148,7 +149,7 @@ mod tests { use super::*; use crate::{ - database::structs::{SActor, SConfig, SRole}, + database::structs::{SConfig, SRole}, rc_refcell, }; use nix::unistd::{Group, Pid}; @@ -157,21 +158,21 @@ mod tests { #[test] fn test_user_contained_in() { let user = User::from_uid(0.into()).unwrap().unwrap(); - let actors = vec![SActor::from_user_id(0)]; + let actors = vec![SActor::user(0).build()]; assert!(user_contained_in(&user, &actors)); } #[test] fn test_group_contained_in() { let group = Group::from_gid(0.into()).unwrap().unwrap(); - let actors = vec![SActor::from_group_id(0)]; + let actors = vec![SActor::group(0).build()]; assert!(group_contained_in(&group, &actors)); } #[test] fn test_groups_subset_of() { let groups = vec![Group::from_gid(0.into()).unwrap().unwrap()]; - let actors = vec![SActor::from_group_id(0)]; + let actors = vec![SActor::group(0).build()]; assert!(groups_subset_of(&groups, &actors)); } @@ -189,7 +190,7 @@ mod tests { let sconfig = SConfig::builder() .role( SRole::builder("role1".to_string()) - .actor(SActor::from_group_id(0)) + .actor(SActor::group(0).build()) .build(), ) .build(); @@ -206,7 +207,7 @@ mod tests { role.as_ref() .borrow_mut() .actors - .push(SActor::from_group_id(0)); + .push(SActor::group(0).build()); role.as_ref().borrow_mut()._extra_fields.insert( "ssd".to_string(), serde_json::Value::Array(vec![Value::String("role1".to_string())]), diff --git a/rar-common/src/util.rs b/rar-common/src/util.rs index 3e24be90..8e24a667 100644 --- a/rar-common/src/util.rs +++ b/rar-common/src/util.rs @@ -257,6 +257,7 @@ pub fn final_path(path: &str) -> PathBuf { #[cfg(debug_assertions)] pub fn subsribe(_: &str) -> Result<(), Box> { env_logger::Builder::from_default_env() + .filter_level(log::LevelFilter::Debug) .format_module_path(true) .init(); Ok(()) diff --git a/src/chsr/cli/data.rs b/src/chsr/cli/data.rs index cdf92fe0..ab2b6235 100644 --- a/src/chsr/cli/data.rs +++ b/src/chsr/cli/data.rs @@ -6,11 +6,12 @@ use linked_hash_set::LinkedHashSet; use pest_derive::Parser; use rar_common::database::{ + actor::{SActor, SGroups, SUserType}, options::{ EnvBehavior, EnvKey, OptType, PathBehavior, SAuthentication, SBounding, SPrivileged, TimestampType, }, - structs::{IdTask, SActor, SActorType, SGroups, SetBehavior}, + structs::{IdTask, SetBehavior}, }; #[derive(Parser)] @@ -74,7 +75,7 @@ pub struct Inputs { pub cmd_policy: Option, pub cmd_id: Option>, pub cred_caps: Option, - pub cred_setuid: Option, + pub cred_setuid: Option, pub cred_setgid: Option, pub cred_policy: Option, pub options: bool, diff --git a/src/chsr/cli/mod.rs b/src/chsr/cli/mod.rs index 1c4ac792..edf76386 100644 --- a/src/chsr/cli/mod.rs +++ b/src/chsr/cli/mod.rs @@ -43,6 +43,7 @@ mod tests { use rar_common::{ database::{ + actor::SActor, options::*, read_json_config, structs::{SCredentials, *}, @@ -62,6 +63,7 @@ mod tests { use test_log::test; fn setup(name: &str) { + let file_path = format!("{}.{}", ROOTASROLE, name); let versionned = Versioning::new( SettingsFile::builder() .storage( @@ -69,7 +71,7 @@ mod tests { .method(StorageMethod::JSON) .settings( RemoteStorageSettings::builder() - .path(format!("{}.{}", ROOTASROLE, name)) + .path(file_path.clone()) .not_immutable() .build(), ) @@ -151,9 +153,9 @@ mod tests { .wildcard_denied("*") .build() }) - .actor(SActor::from_user_id(0)) - .actor(SActor::from_group_id(0)) - .actor(SActor::from_group_vec_string(vec!["groupA", "groupB"])) + .actor(SActor::user(0).build()) + .actor(SActor::group(0).build()) + .actor(SActor::group(["groupA", "groupB"]).build()) .task( STask::builder("t_complete") .options(|opt| { @@ -204,10 +206,10 @@ mod tests { .setgid(["group1", "group2"]) .capabilities( SCapabilities::builder(SetBehavior::All) - .add(Cap::LINUX_IMMUTABLE) - .add(Cap::NET_BIND_SERVICE) - .sub(Cap::SYS_ADMIN) - .sub(Cap::SYS_BOOT) + .add_cap(Cap::LINUX_IMMUTABLE) + .add_cap(Cap::NET_BIND_SERVICE) + .sub_cap(Cap::SYS_ADMIN) + .sub_cap(Cap::SYS_BOOT) .build(), ) .build(), @@ -220,30 +222,15 @@ mod tests { ) .build(), ); - let path = versionned - .data - .storage - .settings - .as_ref() - .unwrap() - .path - .as_ref() - .unwrap(); - let mut file = std::fs::File::create(path).unwrap_or_else(|_| { + let mut file = std::fs::File::create(file_path.clone()).unwrap_or_else(|_| { panic!( "Failed to create {:?}/{:?} file at", current_dir().unwrap(), - path + file_path ) }); - - file.write_all( - serde_json::to_string_pretty(&versionned) - .unwrap() - .as_bytes(), - ) - .unwrap(); - + let jsonstr = serde_json::to_string_pretty(&versionned).unwrap(); + file.write_all(jsonstr.as_bytes()).unwrap(); file.flush().unwrap(); } @@ -438,17 +425,17 @@ mod tests { .as_ref() .borrow() .actors - .contains(&SActor::from_user_string("user1"))); + .contains(&SActor::user("user1").build())); assert!(config.as_ref().borrow()[0] .as_ref() .borrow() .actors - .contains(&SActor::from_group_string("group1"))); + .contains(&SActor::group("group1").build())); assert!(config.as_ref().borrow()[0] .as_ref() .borrow() .actors - .contains(&SActor::from_group_vec_string(vec!["group2", "group3"]))); + .contains(&SActor::group(["group2", "group3"]).build())); assert!(main( &Storage::JSON(config.clone()), "r complete revoke -u user1 -g group1 -g group2&group3".split(" "), @@ -464,17 +451,17 @@ mod tests { .as_ref() .borrow() .actors - .contains(&SActor::from_user_string("user1"))); + .contains(&SActor::user("user1").build())); assert!(!config.as_ref().borrow()[0] .as_ref() .borrow() .actors - .contains(&SActor::from_group_string("group1"))); + .contains(&SActor::group("group1").build())); assert!(!config.as_ref().borrow()[0] .as_ref() .borrow() .actors - .contains(&SActor::from_group_vec_string(vec!["group2", "group3"]))); + .contains(&SActor::group(["group2", "group3"]).build())); teardown("r_complete_grant_u_user1_g_group1_g_group2_group3"); } #[test] diff --git a/src/chsr/cli/pair.rs b/src/chsr/cli/pair.rs index ed9a4911..79f1655b 100644 --- a/src/chsr/cli/pair.rs +++ b/src/chsr/cli/pair.rs @@ -8,10 +8,11 @@ use pest::iterators::Pair; use crate::cli::data::{RoleType, TaskType}; use rar_common::database::{ + actor::{SActor, SGroupType}, options::{ EnvBehavior, OptType, PathBehavior, SAuthentication, SBounding, SPrivileged, TimestampType, }, - structs::{IdTask, SActor, SActorType, SGroups, SetBehavior}, + structs::{IdTask, SetBehavior}, }; use super::data::*; @@ -192,7 +193,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) -> Result<(), Box { @@ -221,15 +222,16 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) -> Result<(), Box>(), + ) + .build(), + ); } debug!("actors: {:?}", inputs.actors); } @@ -265,10 +267,17 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) -> Result<(), Box { - inputs.cred_setuid = Some(pair.as_str().chars().skip(9).collect::().into()); + inputs.cred_setuid = Some( + pair.as_str() + .chars() + .skip(9) + .collect::() + .as_str() + .into(), + ); } Rule::cred_g => { - let mut vec: Vec = Vec::new(); + let mut vec: Vec = Vec::new(); for pair in pair.clone().into_inner() { if pair.as_rule() == Rule::actor_name { vec.push(pair.as_str().into()); @@ -277,11 +286,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) -> Result<(), Box { @@ -426,7 +431,7 @@ mod test { }; use rar_common::{ - database::structs::SActor, + database::actor::SActor, util::{BOLD, RED, RST}, }; @@ -470,9 +475,9 @@ mod test { assert_eq!( inputs.actors, Some(vec![ - SActor::from_user_string("u1"), - SActor::from_user_string("u2"), - SActor::from_group_vec_string(vec!["g1", "g2"]) + SActor::user("u1").build(), + SActor::user("u2").build(), + SActor::group(["g1", "g2"]).build() ]) ); } diff --git a/src/chsr/cli/process/json.rs b/src/chsr/cli/process/json.rs index 3dcc7e74..4fe165ca 100644 --- a/src/chsr/cli/process/json.rs +++ b/src/chsr/cli/process/json.rs @@ -10,7 +10,7 @@ use rar_common::database::{ EnvBehavior, EnvKey, Opt, OptStack, OptType, PathBehavior, SEnvOptions, SPathOptions, STimeout, }, - structs::{IdTask, RoleGetter, SCapabilities, SCommand, SRole, STask}, + structs::{IdTask, RoleGetter, SCapabilities, SCommand, SRole, STask, SUserChooser}, }; use super::perform_on_target_opt; @@ -271,7 +271,7 @@ pub fn grant_revoke( rconfig: &Rc>, role_id: String, action: InputAction, - mut actors: Vec, + mut actors: Vec, ) -> Result> { debug!("chsr role r1 grant|revoke"); let role = rconfig.role(&role_id).ok_or("Role not found")?; @@ -310,8 +310,8 @@ pub fn cred_set( role_id: String, task_id: IdTask, cred_caps: Option, - cred_setuid: Option, - cred_setgid: Option, + cred_setuid: Option, + cred_setgid: Option, ) -> Result> { debug!("chsr role r1 task t1 cred"); match rconfig.task(&role_id, task_id) { @@ -320,7 +320,7 @@ pub fn cred_set( task.as_ref().borrow_mut().cred.capabilities = Some(SCapabilities::from(caps)); } if let Some(setuid) = cred_setuid { - task.as_ref().borrow_mut().cred.setuid = Some(setuid); + task.as_ref().borrow_mut().cred.setuid = Some(SUserChooser::Actor(setuid)); } if let Some(setgid) = cred_setgid { task.as_ref().borrow_mut().cred.setgid = Some(setgid); @@ -336,8 +336,8 @@ pub fn cred_unset( role_id: String, task_id: IdTask, cred_caps: Option, - cred_setuid: Option, - cred_setgid: Option, + cred_setuid: Option, + cred_setgid: Option, ) -> Result> { debug!("chsr role r1 task t1 cred unset"); match rconfig.task(&role_id, task_id) { diff --git a/src/sr/main.rs b/src/sr/main.rs index e0dfa3b8..1a1d684c 100644 --- a/src/sr/main.rs +++ b/src/sr/main.rs @@ -9,8 +9,10 @@ use nix::{ unistd::{getgroups, getuid, isatty, Group, User}, }; use rar_common::database::{ - finder::{Cred, FilterMatcher, TaskMatch, TaskMatcher}, + actor::{SGroups, SUserType}, + finder::{Cred, TaskMatch, TaskMatcher}, options::EnvBehavior, + FilterMatcher, }; use rar_common::database::{options::OptStack, structs::SConfig}; use rar_common::util::escape_parser_string; @@ -23,7 +25,7 @@ use std::{cell::RefCell, error::Error, io::stdout, os::fd::AsRawFd, rc::Rc}; use rar_common::plugin::register_plugins; use rar_common::{ self, - database::{read_json_config, structs::SGroups}, + database::read_json_config, util::{ activates_no_new_privs, dac_override_effective, drop_effective, read_effective, setgid_effective, setpcap_effective, setuid_effective, subsribe, BOLD, RST, UNDERLINE, @@ -65,6 +67,9 @@ const USAGE: &str = formatcp!( [default: "Password: "] + {BOLD}-u, --user {RST} + Specify the user to execute the command as + {BOLD}-i, --info{RST} Display rights of executor @@ -137,10 +142,14 @@ where let mut iter = s.into_iter().skip(1); let mut role = None; let mut task = None; + let mut user: Option = None; let mut env = None; while let Some(arg) = iter.next() { // matches only first options match arg.as_ref() { + "-u" | "--user" => { + user = iter.next().map(|s| escape_parser_string(s).as_str().into()); + } "-S" | "--stdin" => { args.stdin = true; } @@ -180,6 +189,7 @@ where .maybe_role(role) .maybe_task(task) .maybe_env_behavior(env) + .maybe_user(user) .build(), ); for arg in iter { @@ -377,7 +387,7 @@ fn set_capabilities(execcfg: &rar_common::database::finder::ExecSettings, optsta fn setuid_setgid(execcfg: &rar_common::database::finder::ExecSettings) { let uid = execcfg.setuid.as_ref().and_then(|u| { - let res = u.into_user().unwrap_or(None); + let res = u.fetch_user(); if let Some(user) = res { Some(user.uid.as_raw()) } else { @@ -386,7 +396,7 @@ fn setuid_setgid(execcfg: &rar_common::database::finder::ExecSettings) { }); let gid = execcfg.setgroups.as_ref().and_then(|g| match g { SGroups::Single(g) => { - let res = g.into_group().unwrap_or(None); + let res = g.fetch_group(); if let Some(group) = res { Some(group.gid.as_raw()) } else { @@ -394,7 +404,7 @@ fn setuid_setgid(execcfg: &rar_common::database::finder::ExecSettings) { } } SGroups::Multiple(g) => { - let res = g.first().unwrap().into_group().unwrap_or(None); + let res = g.first().unwrap().fetch_group(); if let Some(group) = res { Some(group.gid.as_raw()) } else { @@ -404,7 +414,7 @@ fn setuid_setgid(execcfg: &rar_common::database::finder::ExecSettings) { }); let groups = execcfg.setgroups.as_ref().and_then(|g| match g { SGroups::Single(g) => { - let res = g.into_group().unwrap_or(None); + let res = g.fetch_group(); if let Some(group) = res { Some(vec![group.gid.as_raw()]) } else { @@ -412,7 +422,7 @@ fn setuid_setgid(execcfg: &rar_common::database::finder::ExecSettings) { } } SGroups::Multiple(g) => { - let res = g.iter().map(|g| g.into_group().unwrap_or(None)); + let res = g.iter().map(|g| g.fetch_group()); let mut groups = Vec::new(); for group in res.flatten() { groups.push(group.gid.as_raw()); @@ -432,13 +442,12 @@ fn setuid_setgid(execcfg: &rar_common::database::finder::ExecSettings) { mod tests { use libc::getgid; use nix::unistd::Pid; + use rar_common::database::actor::SActor; use rar_common::rc_refcell; use super::*; use rar_common::database::make_weak_config; - use rar_common::database::structs::{ - IdTask, SActor, SCommand, SCommands, SConfig, SRole, STask, - }; + use rar_common::database::structs::{IdTask, SCommand, SCommands, SConfig, SRole, STask}; #[test] fn test_from_json_execution_settings() { @@ -470,7 +479,7 @@ mod tests { role.as_ref() .borrow_mut() .actors - .push(SActor::from_user_id(0)); + .push(SActor::user(0).build()); role.as_ref().borrow_mut().tasks.push(task); let task = rc_refcell!(STask::default()); task.as_ref().borrow_mut().name = IdTask::Name("task2".to_owned());