From 75585fef95d36e0ac16984a9b197e827f1cf12fb Mon Sep 17 00:00:00 2001 From: LeChatP Date: Sat, 4 May 2024 10:04:25 +0200 Subject: [PATCH] Bugfixes & tests --- Makefile | 2 + configure.sh | 21 +- dependencies.sh | 33 +- resources/rootasrole.json | 27 +- src/api.rs | 5 +- src/chsr/cli.pest | 8 +- src/chsr/cli.rs | 336 +++++++- src/chsr/main.rs | 1605 ++++++++++++++++++++++++++++++++++++- src/config.rs | 32 +- src/database/finder.rs | 6 +- src/database/mod.rs | 34 +- src/database/options.rs | 6 +- src/database/structs.rs | 1 + src/mod.rs | 20 +- src/plugin/hierarchy.rs | 5 +- 15 files changed, 2031 insertions(+), 110 deletions(-) diff --git a/Makefile b/Makefile index 1e4de334..726323df 100644 --- a/Makefile +++ b/Makefile @@ -23,6 +23,8 @@ build: $(BINS) install: build cp -f $(BINS) /usr/bin + chown root:root /usr/bin/sr /usr/bin/chsr /usr/bin/capable + chmod 0555 /usr/bin/sr /usr/bin/chsr /usr/bin/capable setcap "=p" /usr/bin/sr setcap cap_dac_override,cap_sys_admin,cap_sys_ptrace+ep /usr/bin/capable diff --git a/configure.sh b/configure.sh index 80c8e927..275cfd0b 100755 --- a/configure.sh +++ b/configure.sh @@ -18,6 +18,23 @@ if [ $(capsh --has-p=CAP_DAC_OVERRIDE; echo $?) != 0 ] || [ $(capsh --has-p=CAP_ exit 1 fi +export $(grep -h '^ID' /etc/*-release) + +echo "Configuration files installation" +echo "id : ${ID}" +if [ "${ID}" == "arch" ]; then + cp resources/arch_sr_pam.conf /etc/pam.d/sr || exit; +elif [ "${ID}" == "ubuntu" ] || [ "${ID}" == "debian" ]; then + cp resources/deb_sr_pam.conf /etc/pam.d/sr || exit; +elif [ "${ID}" == "centos" ] || [ "${ID}" == "fedora" ] || [[ "${ID}" == *"rhel"* ]]; then + cp resources/rh_sr_pam.conf /etc/pam.d/sr || exit; +else + echo "Unable to find a supported distribution, exiting..." + exit 3 +fi + + + if [ -e "/etc/security/rootasrole.json" ];then if [ $INSTALL_USER == "0" ]; then echo "Warning: You run this script as real root, so the administator role is defined for the root user" @@ -42,4 +59,6 @@ chmod 0644 /etc/pam.d/sr || exit chmod 0640 /etc/security/rootasrole.json || exit if [ $DOCKER -eq 0 ]; then chattr +i /etc/security/rootasrole.json || exit -fi \ No newline at end of file +fi + +echo "Configuration done, Ready to compile." diff --git a/dependencies.sh b/dependencies.sh index ee8ff9df..6d43d6f0 100755 --- a/dependencies.sh +++ b/dependencies.sh @@ -26,6 +26,7 @@ fi if [ ! -f "/usr/bin/cargo" ]; then cp ~/.cargo/bin/cargo /usr/bin + ln -s /usr/local/bin/cargo /bin/cargo echo "as $HOME/.cargo/bin/cargo cargo program is copied to /usr/bin" fi @@ -59,19 +60,6 @@ else exit 2 fi -echo "Install Rust Cargo compiler" -if [ "$(which cargo &>/dev/null ; echo $?)" -eq "0" ]; then - echo "Cargo is installed" -else - curl https://sh.rustup.rs -sSf | sh -s -- --default-toolchain nightly ${YES} -fi - -if [ ! -f "/usr/bin/cargo" ]; then - mv -f ~/.cargo/bin/cargo /usr/local/bin - ln -s /usr/local/bin/cargo /bin/cargo - echo "$HOME/.cargo/bin/cargo program is copied to /usr/local/bin" -fi - # ask for user to install bpf-linker if [ "${YES}" == "-y" ]; then echo "cargo install bpf-linker into /usr/local/bin" @@ -90,21 +78,4 @@ else esac fi -export $(grep -h '^ID' /etc/*-release) - -echo "Configuration files installation" -echo "id : ${ID}" -if [ "${ID}" == "arch" ]; then - cp resources/arch_sr_pam.conf /etc/pam.d/sr || exit; - elif [ "${ID}" == "ubuntu" ] || [ "${ID}" == "debian" ]; then - cp resources/deb_sr_pam.conf /etc/pam.d/sr || exit; - elif [ "${ID}" == "centos" ] || [ "${ID}" == "fedora" ] || [[ "${ID}" == *"rhel"* ]]; then - cp resources/rh_sr_pam.conf /etc/pam.d/sr || exit; -else - echo "Unable to find a supported distribution, exiting..." - exit 3 -fi - - - -echo "configuration done. Ready to compile." +echo "dependencies installed. Ready to compile." diff --git a/resources/rootasrole.json b/resources/rootasrole.json index 67cc113d..958d3094 100644 --- a/resources/rootasrole.json +++ b/resources/rootasrole.json @@ -51,13 +51,13 @@ "TZ" ] }, - "allow-root": false, - "allow-bounding": false, + "root": "user", + "bounding": "strict", "wildcard-denied": ";&|" }, "roles": [ { - "name": "t_root", + "name": "r_root", "actors": [ { "type": "user", @@ -72,12 +72,31 @@ "setuid": "root", "setgid": "root", "capabilities": { - "default": "all" + "default": "all", + "sub": ["CAP_LINUX_IMMUTABLE"] } }, "commands": { "default": "all" } + }, + { + "name": "t_chsr", + "purpose": "Configure RootAsRole", + "cred": { + "setuid": "root", + "setgid": "root", + "capabilities": { + "default": "none", + "add": ["CAP_LINUX_IMMUTABLE"] + } + }, + "commands": { + "default": "none", + "add": [ + "/usr/bin/chsr .*" + ] + } } ] } diff --git a/src/api.rs b/src/api.rs index fea8298e..24178e01 100644 --- a/src/api.rs +++ b/src/api.rs @@ -134,15 +134,16 @@ impl PluginManager { ) -> PluginResultAction { debug!("Notifying role matchers"); let api = API.lock().unwrap(); + let mut result = PluginResultAction::Ignore; for plugin in api.role_matcher_plugins.iter() { debug!("Calling role matcher plugin"); match plugin(role, user, command, matcher) { PluginResultAction::Override => return PluginResultAction::Override, - PluginResultAction::Edit => continue, + PluginResultAction::Edit => result = PluginResultAction::Edit, PluginResultAction::Ignore => continue, } } - PluginResultAction::Ignore + result } pub fn notify_task_matcher( diff --git a/src/chsr/cli.pest b/src/chsr/cli.pest index 8b2430ac..f49f25d8 100644 --- a/src/chsr/cli.pest +++ b/src/chsr/cli.pest @@ -5,7 +5,7 @@ chsr = _{ name } list = { ("show" | "list" | "l") } set = { "set" | "s" } add = { "add" | "create" } -del = { "del" | "delete" | "unset" | "d" | "rm"} +del = { "delete" | "del" | "unset" | "d" | "rm"} purge = { "purge" } grant = { "grant" } revoke = { "revoke" } @@ -141,10 +141,10 @@ options_operations = { ("options" | "o") ~ opt_args } opt_args = _{ opt_show | opt_path | opt_env | opt_root | opt_bounding | opt_wildcard | opt_timeout } opt_show = _{ list ~ opt_show_arg? } -opt_show_arg = { "all" | "cmd" | "cred" | "env" | "root" | "bounding" | "wildcard-denied" | "timeout" } +opt_show_arg = { "all" | "cmd" | "cred" | "path" | "env" | "root" | "bounding" | "wildcard-denied" | "timeout" } opt_path = { "path" ~ (opt_path_args | help) } -opt_path_args = _{ opt_path_set | opt_path_setpolicy | opt_path_listing } +opt_path_args = _{ opt_path_setpolicy | opt_path_set | opt_path_listing } opt_path_set = _{ set ~ path } opt_path_setpolicy = _{ setpolicy ~ path_policy } path_policy = { "delete-all" | "keep-safe" | "keep-unsafe" | "inherit" } @@ -152,7 +152,7 @@ opt_path_listing = { (whitelist | blacklist) ~ (((add | del | set) ~ path) | path = @{ name } opt_env = { "env" ~ (opt_env_args | help) } -opt_env_args = _{ opt_env_set | opt_env_setpolicy | opt_env_listing } +opt_env_args = _{ opt_env_setpolicy | opt_env_set | opt_env_listing } opt_env_setpolicy = { setpolicy ~ env_policy } env_policy = { "delete-all" | "keep-all" | "inherit" } opt_env_listing = { (whitelist | blacklist | checklist) ~ (((add | del | set) ~ env_list) | purge) } diff --git a/src/chsr/cli.rs b/src/chsr/cli.rs index d5677ea4..4e74cf57 100644 --- a/src/chsr/cli.rs +++ b/src/chsr/cli.rs @@ -14,7 +14,6 @@ use linked_hash_set::LinkedHashSet; use pest::{error::LineColLocation, iterators::Pair, Parser}; use pest_derive::Parser; use tracing::{debug, warn}; -use tracing_subscriber::field::debug; use crate::{ common::{ @@ -25,8 +24,7 @@ use crate::{ SPathOptions, SPrivileged, STimeout, TimestampType, }, structs::{ - IdTask, SActor, SActorType, SCapabilities, SCommand, SGroups, SRole, STask, - SetBehavior, + IdTask, SActor, SActorType, SCapabilities, SCommand, SCommands, SGroups, SRole, STask, SetBehavior }, }, util::escape_parser_string, }, @@ -188,6 +186,7 @@ enum InputAction { Add, Del, Purge, + None, } #[derive(Debug, PartialEq, Eq)] @@ -231,7 +230,7 @@ struct Inputs { impl Default for Inputs { fn default() -> Self { Inputs { - action: InputAction::Help, + action: InputAction::None, setlist_type: None, timeout_type: None, timeout_duration: None, @@ -298,6 +297,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { } // === setpolicies === Rule::cmd_policy => { + inputs.action = InputAction::Set; if pair.as_str() == "deny-all" { inputs.cmd_policy = Some(SetBehavior::None); } else if pair.as_str() == "allow-all" { @@ -307,6 +307,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { } } Rule::caps_policy => { + inputs.action = InputAction::Set; if pair.as_str() == "deny-all" { inputs.cred_policy = Some(SetBehavior::None); } else if pair.as_str() == "allow-all" { @@ -316,6 +317,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { } } Rule::path_policy => { + inputs.action = InputAction::Set; if pair.as_str() == "delete-all" { inputs.options_path_policy = Some(PathBehavior::Delete); } else if pair.as_str() == "keep-safe" { @@ -329,6 +331,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { } } Rule::env_policy => { + inputs.action = InputAction::Set; if pair.as_str() == "delete-all" { inputs.options_env_policy = Some(EnvBehavior::Delete); } else if pair.as_str() == "keep-all" { @@ -340,7 +343,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { } } // === timeout === - Rule::opt_timeout_d_arg => { + Rule::time => { let mut reversed = pair.as_str().split(':').rev(); let mut duration: Duration = Duration::try_seconds(reversed.next().unwrap().parse::().unwrap_or(0)) @@ -362,7 +365,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { } inputs.timeout_duration = Some(duration); } - Rule::opt_timeout_t_arg => { + Rule::opt_timeout_type => { if pair.as_str() == "tty" { inputs.timeout_type = Some(TimestampType::TTY); } else if pair.as_str() == "ppid" { @@ -373,7 +376,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { warn!("Unknown timeout type: {}", pair.as_str()) } } - Rule::opt_timeout_m_arg => { + Rule::opt_timeout_max_usage => { inputs.timeout_max_usage = Some(pair.as_str().parse::().unwrap()); } // === roles === @@ -513,6 +516,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { .insert_if_absent(pair.as_str().into()); } Rule::opt_root_args => { + inputs.action = InputAction::Set; if pair.as_str() == "privileged" { inputs.options_root = Some(SPrivileged::Privileged); } else if pair.as_str() == "user" { @@ -524,6 +528,7 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { } } Rule::opt_bounding_args => { + inputs.action = InputAction::Set; if pair.as_str() == "strict" { inputs.options_bounding = Some(SBounding::Strict); } else if pair.as_str() == "ignore" { @@ -537,6 +542,13 @@ fn match_pair(pair: &Pair, inputs: &mut Inputs) { Rule::wildcard_value => { inputs.options_wildcard = Some(pair.as_str().to_string()); } + Rule::all => { + if inputs.role_id.is_some() && !inputs.task_id.is_some() { + inputs.role_type = Some(RoleType::All); + } else if inputs.task_id.is_some() { + inputs.task_type = Some(TaskType::All); + } + } _ => { debug!("Unmatched rule: {:?}", pair.as_rule()); } @@ -668,14 +680,18 @@ fn usage_concat(usages: &[&'static str]) -> String { } -pub fn main(storage: &Storage) -> Result> { +pub fn main(storage: &Storage, args : I) -> Result> +where + I: IntoIterator, + S: AsRef, +{ /*let binding = std::env::args().fold("\"".to_string(), |mut s, e| { s.push_str(&e); s.push_str("\" \""); s });*/ - let args = escape_parser_string(std::env::args()); + let args = escape_parser_string(args); let args = Cli::parse(Rule::cli, &args); let args = match args { Ok(v) => v, @@ -750,12 +766,13 @@ pub fn main(storage: &Storage) -> Result> { for pair in args { recurse_pair(pair, &mut inputs); } - + debug!("Inputs : {:?}", inputs); match inputs { Inputs { action: InputAction::Help, .. } => { + debug!("chsr help"); println!("{}", LONG_ABOUT); println!("{}", RAR_USAGE_GENERAL); Ok(false) @@ -770,16 +787,27 @@ pub fn main(storage: &Storage) -> Result> { options_type, // in json .. } => match storage { - Storage::JSON(rconfig) => list_json( - rconfig, - role_id, - task_id, - options, - options_type, - task_type, - role_type, - ) - .and(Ok(false)), + Storage::JSON(rconfig) => { + debug!("chsr list"); + return match list_json( + rconfig, + role_id, + task_id, + options, + options_type, + task_type, + role_type, + ) { + Ok(_) => { + debug!("chsr list ok"); + Ok(false) + } + Err(e) => { + debug!("chsr list err {:?}", e); + Err(e) + } + }; + }, }, Inputs { // chsr role r1 add|del @@ -789,6 +817,7 @@ pub fn main(storage: &Storage) -> Result> { setlist_type: None, options: false, actors: None, + role_type, .. } => match storage { Storage::JSON(rconfig) => { @@ -811,6 +840,25 @@ pub fn main(storage: &Storage) -> Result> { } config.roles.retain(|r| r.as_ref().borrow().name != role_id); Ok(true) + }, + InputAction::Purge => { + if config.role(&role_id).is_none() { + return Err("Role do not exists".into()); + } + let role = config.role(&role_id).unwrap(); + match role_type { + Some(RoleType::Actors) => { + role.as_ref().borrow_mut().actors.clear(); + } + Some(RoleType::Tasks) => { + role.as_ref().borrow_mut().tasks.clear(); + } + None | Some(RoleType::All) => { + role.as_ref().borrow_mut().actors.clear(); + role.as_ref().borrow_mut().tasks.clear(); + } + } + Ok(true) } _ => Ok(false), } @@ -870,6 +918,9 @@ pub fn main(storage: &Storage) -> Result> { cred_caps: None, cred_setuid: None, cred_setgid: None, + task_type, + cmd_policy: None, + cred_policy: None, .. } => match storage { Storage::JSON(rconfig) => { @@ -900,12 +951,37 @@ pub fn main(storage: &Storage) -> Result> { .retain(|t| t.as_ref().borrow().name != task_id); Ok(true) } + InputAction::Purge => { + let borrow = &role.as_ref().borrow(); + let task = borrow.task(&task_id).expect("Task do not exists".into()); + match task_type { + Some(TaskType::Commands) => { + task.as_ref().borrow_mut().commands.add.clear(); + task.as_ref().borrow_mut().commands.sub.clear(); + task.as_ref().borrow_mut().commands.default_behavior = None; + } + Some(TaskType::Credentials) => { + task.as_ref().borrow_mut().cred.capabilities = None; + task.as_ref().borrow_mut().cred.setuid = None; + task.as_ref().borrow_mut().cred.setgid = None; + } + None | Some(TaskType::All) => { + task.as_ref().borrow_mut().commands.add.clear(); + task.as_ref().borrow_mut().commands.sub.clear(); + task.as_ref().borrow_mut().commands.default_behavior = None; + task.as_ref().borrow_mut().cred.capabilities = None; + task.as_ref().borrow_mut().cred.setuid = None; + task.as_ref().borrow_mut().cred.setgid = None; + } + } + Ok(true) + } _ => Ok(false), } } }, Inputs { - //chsr role r1 task t1 cred --caps "cap_net_raw,cap_sys_admin" + //chsr role r1 task t1 cred set --caps "cap_net_raw,cap_sys_admin" action: InputAction::Set, role_id: Some(role_id), task_id: Some(task_id), @@ -913,6 +989,7 @@ pub fn main(storage: &Storage) -> Result> { cred_setuid, cred_setgid, cmd_id: None, + cmd_policy: None, .. } => match storage { Storage::JSON(rconfig) => { @@ -936,12 +1013,51 @@ pub fn main(storage: &Storage) -> Result> { } } }, + Inputs { + //chsr role r1 task t1 cred unset --caps "cap_net_raw,cap_sys_admin" + action: InputAction::Del, + role_id: Some(role_id), + task_id: Some(task_id), + cred_caps, + cred_setuid, + cred_setgid, + cmd_id: None, + cmd_policy: None, + .. + } => match storage { + Storage::JSON(rconfig) => { + debug!("chsr role r1 task t1 cred unset"); + let config = rconfig.as_ref().borrow_mut(); + match config.task(&role_id, &task_id) { + Ok(task) => { + if let Some(caps) = cred_caps { + if caps.is_empty() { + task.as_ref().borrow_mut().cred.capabilities = None; + } else if let Some(ccaps) = task.as_ref().borrow_mut().cred.capabilities.as_mut() { + ccaps.add.drop_all(caps); + } else { + return Err("No capabilities to remove".into()); + } + } + if let Some(_) = cred_setuid { + task.as_ref().borrow_mut().cred.setuid = None; + } + if let Some(_) = cred_setgid { + task.as_ref().borrow_mut().cred.setgid = None; + } + Ok(true) + } + Err(e) => Err(e), + } + } + }, Inputs { action, role_id: Some(role_id), task_id: Some(task_id), setlist_type: Some(setlist_type), cred_caps: Some(cred_caps), + cmd_policy: None, .. } => match storage { Storage::JSON(rconfig) => { @@ -1041,6 +1157,24 @@ pub fn main(storage: &Storage) -> Result> { Ok(true) } }, + Inputs { + action: InputAction::Set, + role_id: Some(role_id), + task_id: Some(task_id), + cmd_policy: Some(cmd_policy), + .. + } => match storage { + Storage::JSON(rconfig) => { + debug!("chsr role r1 task t1 cmd setpolicy"); + let config = rconfig.as_ref().borrow_mut(); + let task = config.task(&role_id, &task_id)?; + + task.as_ref() + .borrow_mut() + .commands.default_behavior.replace(cmd_policy); + Ok(true) + } + }, Inputs { // chsr role r1 task t1 command whitelist add c1 action, @@ -1193,7 +1327,7 @@ pub fn main(storage: &Storage) -> Result> { }, Inputs { // chsr o wildcard-denied set ";&*$" - action: InputAction::Set, + action, role_id, task_id, options_wildcard: Some(options_wildcard), @@ -1201,7 +1335,34 @@ pub fn main(storage: &Storage) -> Result> { } => match storage { Storage::JSON(rconfig) => { perform_on_target_opt(rconfig, role_id, task_id, |opt: Rc>| { - opt.as_ref().borrow_mut().wildcard_denied = Some(options_wildcard.clone()); + match action { + InputAction::Set => { + opt.as_ref().borrow_mut().wildcard_denied = Some(options_wildcard.clone()); + } + InputAction::Add => { + let mut default_wildcard = opt.as_ref().borrow().wildcard_denied.clone().unwrap_or_default(); + default_wildcard.extend(options_wildcard.chars()); + opt.as_ref().borrow_mut().wildcard_denied = Some(default_wildcard); + } + InputAction::Del => { + if opt.as_ref().borrow().wildcard_denied.is_none() { + println!("No wildcard denied configured"); + return Ok(()); + } + opt.as_ref().borrow_mut().wildcard_denied.as_mut().map(|w| { + w.retain(|c| !options_wildcard.contains(c)); + }); + return Ok(()); + } + InputAction::Purge => { + opt.as_ref().borrow_mut().wildcard_denied = None; + return Ok(()); + } + _ => { + return Err("Unknown action".into()); + } + } + Ok(()) })?; Ok(true) @@ -1233,7 +1394,37 @@ pub fn main(storage: &Storage) -> Result> { return Err("Unknown setlist type".into()); } } - opt.as_ref().borrow_mut().path.as_mut().replace(path); + Ok(()) + })?; + Ok(true) + } + }, + Inputs { + // chsr o path whitelist set a:b:c + action: InputAction::Purge, + role_id, + task_id, + options_path: None, + options_type: Some(OptType::Path), + setlist_type, + .. + } => match storage { + Storage::JSON(rconfig) => { + perform_on_target_opt(rconfig, role_id, task_id, |opt: Rc>| { + let mut default_path = SPathOptions::default(); + let mut binding = opt.as_ref().borrow_mut(); + let path = binding.path.as_mut().unwrap_or(&mut default_path); + match setlist_type { + Some(SetListType::WhiteList) => { + path.add.clear(); + } + Some(SetListType::BlackList) => { + path.sub.clear(); + } + _ => { + return Err("Unknown setlist type".into()); + } + } Ok(()) })?; Ok(true) @@ -1268,7 +1459,6 @@ pub fn main(storage: &Storage) -> Result> { return Err("Internal Error: setlist type not found".into()); } } - opt.as_ref().borrow_mut().env.as_mut().replace(env); Ok(()) })?; Ok(true) @@ -1350,15 +1540,102 @@ pub fn main(storage: &Storage) -> Result> { return Err("Unknown setlist type".into()); } } - opt.as_ref().borrow_mut().path.as_mut().replace(path); Ok(()) })?; Ok(true) } }, - //TODO: implement option env whitelist/blacklist add/delete + Inputs { + // chsr o path whitelist add path1:path2:path3 + action, + role_id, + task_id, + options_env, + options_type: Some(OptType::Env), + setlist_type: Some(setlist_type), + .. + } => match storage { + Storage::JSON(rconfig) => { + perform_on_target_opt(rconfig, role_id, task_id, move |opt: Rc>| { + let mut default_env = SEnvOptions::default(); + let mut binding = opt.as_ref().borrow_mut(); + let env = binding.env.as_mut().unwrap_or(&mut default_env); + match setlist_type { + SetListType::WhiteList => { + match action { + InputAction::Add => { + if options_env.is_none() { + return Err("Empty list".into()); + } + env.keep.extend(options_env.as_ref().unwrap().clone()); + } + InputAction::Del => { + if options_env.is_none() { + return Err("Empty list".into()); + } + env.keep = env.keep.difference(&options_env.as_ref().unwrap().iter().cloned().collect::>()).cloned().collect::>(); + } + InputAction::Purge => { + env.keep = LinkedHashSet::new(); + } + _ => { + return Err("Unknown action".into()); + } + } + } + SetListType::BlackList => { + match action { + InputAction::Add => { + if options_env.is_none() { + return Err("Empty list".into()); + } + env.delete.extend(options_env.as_ref().unwrap().clone()); + } + InputAction::Del => { + if options_env.is_none() { + return Err("Empty list".into()); + } + env.delete = env.delete.difference(options_env.as_ref().unwrap()).cloned().collect::>(); + } + InputAction::Purge => { + env.delete = LinkedHashSet::new(); + } + _ => { + return Err("Unknown action".into()); + } + } + } + SetListType::CheckList => { + match action { + InputAction::Add => { + if options_env.is_none() { + return Err("Empty list".into()); + } + env.check.extend(options_env.as_ref().unwrap().clone()); + } + InputAction::Del => { + if options_env.is_none() { + return Err("Empty list".into()); + } + env.check = env.check.difference(options_env.as_ref().unwrap()).cloned().collect::>(); + } + InputAction::Purge => { + env.check = LinkedHashSet::new(); + } + _ => { + return Err("Unknown action".into()); + } + } + } + } + Ok(()) + })?; + Ok(true) + } + }, _ => Err("Unknown action".into()), } + } fn perform_on_target_opt( rconfig: &Rc>, @@ -1408,6 +1685,7 @@ fn list_json( role_type: Option, ) -> Result<(), Box> { let config = rconfig.as_ref().borrow(); + debug!("list_json {:?}", config); if let Some(role_id) = role_id { if let Some(role) = config.role(&role_id) { list_task(task_id, role, options, options_type, task_type, role_type) @@ -1503,6 +1781,8 @@ fn print_task( mod tests { use super::*; + + fn make_args(args: &str) -> String { shell_words::join(shell_words::split(args).unwrap()) } @@ -1584,4 +1864,6 @@ mod tests { assert_eq!(inputs.role_id, Some("r1".to_string())); assert_eq!(inputs.role_type, Some(RoleType::All)); } + + } diff --git a/src/chsr/main.rs b/src/chsr/main.rs index 0546a442..f8994fea 100644 --- a/src/chsr/main.rs +++ b/src/chsr/main.rs @@ -18,8 +18,7 @@ fn main() -> Result<(), Box> { subsribe("chsr"); drop_effective()?; register_plugins(); - read_effective(true).expect("Operation not permitted"); - let settings = config::get_settings().expect("Failed to get settings"); + let settings = config::get_settings().expect("Error on config read"); let config = match settings.clone().as_ref().borrow().storage.method { config::StorageMethod::JSON => Storage::JSON(read_json_config(settings.clone())?), _ => { @@ -29,7 +28,7 @@ fn main() -> Result<(), Box> { }; read_effective(false).expect("Operation not permitted"); - if cli::main(&config).is_ok_and(|b| b) { + if cli::main(&config, std::env::args()).is_ok_and(|b| b) { match config { Storage::JSON(config) => { debug!("Saving configuration"); @@ -41,3 +40,1603 @@ fn main() -> Result<(), Box> { Ok(()) } } + +#[cfg(test)] +mod tests { + use std::{io::Write, rc::Rc}; + + use self::common::{config::{RemoteStorageSettings, SettingsFile, ROOTASROLE}, database::{options::*, structs::*, version::Versioning}}; + + use super::*; + use capctl::Cap; + use chrono::TimeDelta; + use common::config::Storage; + + fn setup() { + //Write json test json file + let mut file = std::fs::File::create(ROOTASROLE).unwrap(); + let mut settings = SettingsFile::default(); + settings.storage.method = config::StorageMethod::JSON; + settings.storage.settings = Some(RemoteStorageSettings::default()); + settings.storage.settings.as_mut().unwrap().path = Some(ROOTASROLE.into()); + settings.storage.settings.as_mut().unwrap().immutable = Some(false); + + let mut opt = Opt::default(); + + opt.timeout = Some(STimeout::default()); + opt.timeout.as_mut().unwrap().type_field = TimestampType::PPID; + opt.timeout.as_mut().unwrap().duration = TimeDelta::hours(15).checked_add(&TimeDelta::minutes(30)).unwrap().checked_add(&TimeDelta::seconds(30)).unwrap(); + opt.timeout.as_mut().unwrap().max_usage = Some(1); + + opt.path = Some(SPathOptions::default()); + opt.path.as_mut().unwrap().default_behavior = PathBehavior::Delete; + opt.path.as_mut().unwrap().add = vec!["path1".to_string(), "path2".to_string()].into_iter().collect(); + opt.path.as_mut().unwrap().sub = vec!["path3".to_string(), "path4".to_string()].into_iter().collect(); + + opt.env = Some(SEnvOptions::default()); + opt.env.as_mut().unwrap().default_behavior = EnvBehavior::Delete; + opt.env.as_mut().unwrap().keep = vec!["env1".into(), "env2".into()].into_iter().collect(); + opt.env.as_mut().unwrap().check = vec!["env3".into(), "env4".into()].into_iter().collect(); + opt.env.as_mut().unwrap().delete = vec!["env5".into(), "env6".into()].into_iter().collect(); + + opt.root = Some(SPrivileged::Privileged); + opt.bounding = Some(SBounding::Ignore); + opt.wildcard_denied = Some("*".to_string()); + + settings.config.as_ref().borrow_mut().options = Some(rc_refcell!(opt.clone())); + + settings.config.as_ref().borrow_mut().roles = vec![]; + + let mut role = SRole::default(); + role.name = "complete".to_string(); + role.actors = vec![SActor::from_user_id(0),SActor::from_group_id(0),SActor::from_group_vec_string(vec!["groupA","groupB"])]; + role.options = Some(rc_refcell!(opt.clone())); + let role = rc_refcell!(role); + + let mut task = STask::new(IdTask::Name("t_complete".to_string()), Rc::downgrade(&role)); + task.purpose = Some("complete".to_string()); + task.commands = SCommands::default(); + task.commands.default_behavior = Some(SetBehavior::All); + task.commands.add.push(SCommand::Simple("ls".to_string())); + task.commands.add.push(SCommand::Simple("echo".to_string())); + task.commands.sub.push(SCommand::Simple("cat".to_string())); + task.commands.sub.push(SCommand::Simple("grep".to_string())); + + task.cred = SCredentials::default(); + task.cred.setuid = Some(SActorType::Name("user1".to_string())); + task.cred.setgid = Some(SGroups::Multiple(vec![SActorType::Name("group1".to_string()),SActorType::Name("group2".to_string())])); + task.cred.capabilities = Some(SCapabilities::default()); + task.cred.capabilities.as_mut().unwrap().default_behavior = SetBehavior::All; + task.cred.capabilities.as_mut().unwrap().add.add(Cap::LINUX_IMMUTABLE); + task.cred.capabilities.as_mut().unwrap().add.add(Cap::NET_BIND_SERVICE); + task.cred.capabilities.as_mut().unwrap().sub.add(Cap::SYS_ADMIN); + task.cred.capabilities.as_mut().unwrap().sub.add(Cap::SYS_BOOT); + + task.options = Some(rc_refcell!(opt.clone())); + + role.as_ref().borrow_mut().tasks.push(rc_refcell!(task)); + settings.config.as_ref().borrow_mut().roles.push(role); + + let versionned = Versioning::new(settings.clone()); + + file.write_all(serde_json::to_string_pretty(&versionned).unwrap().as_bytes()).unwrap(); + + file.flush().unwrap(); + } + + fn teardown() { + //Remove json test file + std::fs::remove_file("target/rootasrole.json").unwrap(); + } + // we need to test every commands + // chsr r r1 create + // chsr r r1 delete + // chsr r r1 show (actors|tasks|all) + // chsr r r1 purge (actors|tasks|all) + // chsr r r1 grant -u user1 -g group1 group2&group3 + // chsr r r1 revoke -u user1 -g group1 group2&group3 + // chsr r r1 task t1 show (all|cmd|cred) + // chsr r r1 task t1 purge (all|cmd|cred) + // chsr r r1 t t1 add + // chsr r r1 t t1 del + // chsr r r1 t t1 commands show + // chsr r r1 t t1 cmd setpolicy (deny-all|allow-all) + // chsr r r1 t t1 cmd (whitelist|blacklist) (add|del) super command with spaces + // chsr r r1 t t1 credentials show + // chsr r r1 t t1 cred (unset|set) --caps capA,capB,capC --setuid user1 --setgid group1,group2 + // chsr r r1 t t1 cred caps setpolicy (deny-all|allow-all) + // chsr r r1 t t1 cred caps (whitelist|blacklist) (add|del) capA capB capC + // chsr (r r1) (t t1) options show (all|path|env|root|bounding|wildcard-denied) + // chsr o path set /usr/bin:/bin this regroups setpolicy delete and whitelist set + // chsr o path setpolicy (delete-all|keep-all|inherit) + // chsr o path (whitelist|blacklist) (add|del|set|purge) /usr/bin:/bin + + // chsr o env set MYVAR=1 VAR2=2 //this regroups setpolicy delete and whitelist set + // chsr o env setpolicy (delete-all|keep-all|inherit) + // chsr o env (whitelist|blacklist|checklist) (add|del|set|purge) MYVAR=1 + + // chsr o root (privileged|user|inherit) + // chsr o bounding (strict|ignore|inherit) + // chsr o wildcard-denied (set|add|del) * + + // chsr o timeout set --type tty --duration 5:00 --max_usage 1 + // chsr o t unset --type --duration --max_usage + + //TODO: verify values + #[test] + fn test_main_1() { + setup(); + + // lets test every commands + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "r1", "create"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "r1", "delete"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "show", "actors"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "show", "tasks"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "show", "all"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "purge", "actors"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "purge", "tasks"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "purge", "all"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "grant", + "-u", + "user1", + "-g", + "group1", + "-g", + "group2&group3" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "revoke", + "-u", + "user1", + "-g", + "group1", + "-g", + "group2&group3" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "task", "t_complete", "show", "all"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "task", "t_complete", "show", "cmd"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "task", "t_complete", "show", "cred"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "task", "t_complete", "purge", "all"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "task", "t_complete", "purge", "cmd"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "task", "t_complete", "purge", "cred"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t1", "add"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t1", "del"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "cmd", "setpolicy", "deny-all"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cmd", + "setpolicy", + "allow-all" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cmd", + "whitelist", + "add", + "super command with spaces" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cmd", + "blacklist", + "add", + "super", + "command", + "with", + "spaces" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cmd", + "whitelist", + "del", + "super command with spaces" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cmd", + "blacklist", + "del", + "super command with spaces" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + // let settings = config::get_settings().expect("Failed to get settings"); + // assert!(cli::main( + // &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + // vec!["chsr", "r", "complete", "t", "t_complete", "credentials", "show"], + // ) + // .inspect_err(|e| { + // error!("{}", e); + // }) + // .inspect(|e| { + // debug!("{}",e); + // }) + // .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cred", + "unset", + "--caps", + "capA,capB,capC", + "--setuid", + "user1", + "--setgid", + "group1,group2" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cred", + "set", + "--caps", + "capA,capB,capC", + "--setuid", + "user1", + "--setgid", + "group1,group2" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cred", + "caps", + "setpolicy", + "deny-all" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cred", + "caps", + "setpolicy", + "allow-all" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cred", + "caps", + "whitelist", + "add", + "capA", + "capB", + "capC" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cred", + "caps", + "blacklist", + "add", + "capA", + "capB", + "capC" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cred", + "caps", + "whitelist", + "del", + "capA", + "capB", + "capC" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "cred", + "caps", + "blacklist", + "del", + "capA", + "capB", + "capC" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "options", "show", "all"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "options", "show", "path"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "options", "show", "bounding"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "options", "show", "env"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "options", "show", "root"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "options", "show", "bounding"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "options", + "show", + "wildcard-denied" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| !b)); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "set", + "/usr/bin:/bin" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "setpolicy", + "delete-all" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "setpolicy", + "keep-unsafe" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "setpolicy", + "inherit" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "whitelist", + "add", + "/usr/bin:/bin" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "whitelist", + "del", + "/usr/bin:/bin" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "whitelist", + "set", + "/usr/bin:/bin" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "whitelist", + "purge" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "blacklist", + "add", + "/usr/bin:/bin" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "blacklist", + "del", + "/usr/bin:/bin" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "blacklist", + "set", + "/usr/bin:/bin" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "path", + "blacklist", + "purge" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "set", + "MYVAR,VAR2" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "setpolicy", + "delete-all" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "setpolicy", + "keep-all" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "setpolicy", + "inherit" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "whitelist", + "add", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "whitelist", + "del", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "whitelist", + "set", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "whitelist", + "purge" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "blacklist", + "add", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "blacklist", + "del", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "blacklist", + "set", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "blacklist", + "purge" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "checklist", + "add", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "checklist", + "del", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "checklist", + "set", + "MYVAR" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "env", + "checklist", + "purge" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "o", "root", "privileged"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "o", "root", "user"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "o", "root", "inherit"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "o", "bounding", "strict"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "o", "bounding", "ignore"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec!["chsr", "r", "complete", "t", "t_complete", "o", "bounding", "inherit"], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "wildcard-denied", + "set", + "*" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "wildcard-denied", + "add", + "*" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "wildcard-denied", + "del", + "*" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "timeout", + "set", + "--type", + "tty", + "--duration", + "5:00", + "--max-usage", + "1" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + let settings = config::get_settings().expect("Failed to get settings"); + assert!(cli::main( + &Storage::JSON(read_json_config(settings.clone()).expect("Failed to read json")), + vec![ + "chsr", + "r", + "complete", + "t", + "t_complete", + "o", + "t", + "unset", + "--type", + "--duration", + "--max-usage" + ], + ) + .inspect_err(|e| { + error!("{}", e); + }) + .inspect(|e| { + debug!("{}",e); + }) + .is_ok_and(|b| b)); + teardown(); + } +} diff --git a/src/config.rs b/src/config.rs index 30eedeb3..5abac578 100644 --- a/src/config.rs +++ b/src/config.rs @@ -47,15 +47,19 @@ // } // } +#[cfg(not(test))] pub const ROOTASROLE: &str = "/etc/security/rootasrole.json"; +#[cfg(test)] +pub const ROOTASROLE: &str = "target/rootasrole.json"; -use std::{cell::RefCell, error::Error, path::PathBuf, rc::Rc}; +use std::{cell::RefCell, error::Error, fs::File, path::PathBuf, rc::Rc}; +use ciborium::de; use serde::{Deserialize, Serialize}; use tracing::debug; use crate::{ - common::{dac_override_effective, immutable_effective, util::toggle_lock_config}, + common::{dac_override_effective, immutable_effective, read_effective, util::toggle_lock_config, write_json_config}, rc_refcell, }; @@ -196,19 +200,11 @@ impl Default for RemoteStorageSettings { } } -fn write_json_config( - settings: &Versioning>>, -) -> Result<(), Box> { - let file = std::fs::File::create(ROOTASROLE)?; - serde_json::to_writer_pretty(file, &settings)?; - Ok(()) -} + pub fn save_settings(settings: Rc>) -> Result<(), Box> { debug!("Setting immutable privilege"); immutable_effective(true)?; - debug!("Setting dac privilege"); - dac_override_effective(true)?; let default_remote: RemoteStorageSettings = RemoteStorageSettings::default(); // remove immutable flag let into = ROOTASROLE.into(); @@ -225,7 +221,7 @@ pub fn save_settings(settings: Rc>) -> Result<(), Box>> = Versioning::new(settings.clone()); - write_json_config(&versionned)?; + write_json_config(&versionned, ROOTASROLE)?; debug!("Toggling immutable off for config file"); toggle_lock_config(path, false)?; debug!("Resetting dac privilege"); @@ -240,8 +236,16 @@ pub fn get_settings() -> Result>, Box> { if !std::path::Path::new(ROOTASROLE).exists() { return Ok(rc_refcell!(SettingsFile::default())); } - let file = std::fs::File::open(ROOTASROLE).expect("Failed to open file"); - let value: Versioning = serde_json::from_reader(file).unwrap_or_default(); + // if user does not have read permission, try to enable privilege + let file = std::fs::File::open(ROOTASROLE).or_else(|e| { + debug!("Error opening file without privilege, trying with privileges: {}", e); + read_effective(true).or(dac_override_effective(true))?; + std::fs::File::open(ROOTASROLE) + })?; + let value: Versioning = serde_json::from_reader(file).inspect_err(|e| { + debug!("Error reading file: {}", e); + }).unwrap_or_default(); + read_effective(false).or(dac_override_effective(false))?; debug!("{}", serde_json::to_string_pretty(&value)?); let settingsfile = rc_refcell!(value.data); if Migration::migrate( diff --git a/src/database/finder.rs b/src/database/finder.rs index 2bff8e1c..b4fe59bd 100644 --- a/src/database/finder.rs +++ b/src/database/finder.rs @@ -819,8 +819,8 @@ fn plugin_role_match( } PluginResultAction::Edit => { debug!("Plugin edit"); - if !min_role.fully_matching() - || (matcher.fully_matching() && matcher.score < min_role.score) + if !min_role.command_matching() + || (matcher.command_matching() && matcher.score.cmd_min < min_role.score.cmd_min) { *min_role = matcher; *nmatch = 1; @@ -829,9 +829,11 @@ fn plugin_role_match( } else if !matcher.fully_matching() { *nmatch = 0; } + } PluginResultAction::Ignore => {} } + debug!("nmatch = {}", nmatch); } impl TaskMatcher for Rc> { diff --git a/src/database/mod.rs b/src/database/mod.rs index 7092468e..214f93de 100644 --- a/src/database/mod.rs +++ b/src/database/mod.rs @@ -1,6 +1,7 @@ use std::{cell::RefCell, error::Error, rc::Rc}; use crate::common::config::save_settings; +use crate::common::read_effective; use crate::common::util::toggle_lock_config; use crate::common::version::PACKAGE_VERSION; @@ -13,6 +14,7 @@ use self::{migration::Migration, options::EnvKey, structs::SConfig, version::Ver use super::config::SettingsFile; use super::util::warn_if_mutable; +use super::write_json_config; use super::{ config::{RemoteStorageSettings, ROOTASROLE}, dac_override_effective, immutable_effective, @@ -102,41 +104,39 @@ pub fn save_json( } debug!("Setting immutable privilege"); immutable_effective(true)?; - debug!("Setting dac privilege"); - dac_override_effective(true)?; debug!("Toggling immutable on for config file"); toggle_lock_config(path, true)?; + immutable_effective(false)?; debug!("Writing config file"); let versionned: Versioning>> = Versioning { version: PACKAGE_VERSION.to_owned().parse()?, data: config, }; - write_json_config(&settings.as_ref().borrow(), versionned)?; + write_sconfig(&settings.as_ref().borrow(), versionned)?; debug!("Toggling immutable off for config file"); + immutable_effective(true)?; toggle_lock_config(path, false)?; - debug!("Resetting dac privilege"); - dac_override_effective(false)?; + debug!("Resetting immutable privilege"); immutable_effective(false)?; Ok(()) } -fn write_json_config( +fn write_sconfig( settings: &SettingsFile, config: Versioning>>, ) -> Result<(), Box> { let default_remote = RemoteStorageSettings::default(); - let file = std::fs::File::create( - settings - .storage - .settings - .as_ref() - .unwrap_or(&default_remote) - .path - .as_ref() - .unwrap_or(&ROOTASROLE.into()), - )?; - serde_json::to_writer_pretty(file, &config)?; + let binding = ROOTASROLE.into(); + let path = settings + .storage + .settings + .as_ref() + .unwrap_or(&default_remote) + .path + .as_ref() + .unwrap_or(&binding); + write_json_config(&config, path); Ok(()) } diff --git a/src/database/options.rs b/src/database/options.rs index c32a0186..f00aa853 100644 --- a/src/database/options.rs +++ b/src/database/options.rs @@ -2,6 +2,7 @@ use std::{borrow::Borrow, cell::RefCell, path::PathBuf, rc::Rc}; use chrono::Duration; +use ciborium::de; use libc::PATH_MAX; use linked_hash_set::LinkedHashSet; use pcre2::bytes::Regex; @@ -134,7 +135,7 @@ pub struct EnvKey { value: String, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] pub struct SEnvOptions { #[serde(rename = "default", default, skip_serializing_if = "is_default")] pub default_behavior: EnvBehavior, @@ -183,7 +184,7 @@ pub enum SPrivileged { Inherit, } -#[derive(Serialize, Deserialize, PartialEq, Eq, Debug)] +#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)] #[serde(rename_all = "kebab-case")] pub struct Opt { #[serde(skip_serializing_if = "Option::is_none")] @@ -253,6 +254,7 @@ impl Default for SPathOptions { impl EnvKey { pub fn new(s: String) -> Result { + debug!("Creating env key: {}", s); if Regex::new("^[a-zA-Z_]+[a-zA-Z0-9_]*$") // check if it is a valid env name .unwrap() .is_match(s.as_bytes()) diff --git a/src/database/structs.rs b/src/database/structs.rs index 0086a536..f06aae73 100644 --- a/src/database/structs.rs +++ b/src/database/structs.rs @@ -10,6 +10,7 @@ use serde::{ }; use serde_json::{Map, Value}; use strum::{Display, EnumIs}; +use tracing::debug; use std::{ cell::RefCell, diff --git a/src/mod.rs b/src/mod.rs index 2d454797..86622e02 100644 --- a/src/mod.rs +++ b/src/mod.rs @@ -1,8 +1,11 @@ use capctl::{prctl, Cap, CapState}; -use std::ffi::CString; -use tracing::Level; +use serde::Serialize; +use std::{error::Error, ffi::CString, path::PathBuf}; +use tracing::{debug, Level}; use tracing_subscriber::util::SubscriberInitExt; +use self::config::ROOTASROLE; + pub mod api; pub mod config; pub mod database; @@ -92,3 +95,16 @@ pub fn immutable_effective(enable: bool) -> Result<(), capctl::Error> { pub fn activates_no_new_privs() -> Result<(), capctl::Error> { prctl::set_no_new_privs() } + +pub fn write_json_config>( + settings: &T, + path: S, +) -> Result<(), Box> { + let file = std::fs::File::create(path).or_else(|e| { + debug!("Error creating file without privilege, trying with privileges: {}", e); + read_effective(true).or(dac_override_effective(true))?; + std::fs::File::create(ROOTASROLE) + })?; + serde_json::to_writer_pretty(file, &settings)?; + Ok(()) +} \ No newline at end of file diff --git a/src/plugin/hierarchy.rs b/src/plugin/hierarchy.rs index d297e7d3..63bdd1fb 100644 --- a/src/plugin/hierarchy.rs +++ b/src/plugin/hierarchy.rs @@ -8,6 +8,7 @@ use crate::common::{ }, }; +use ciborium::de; use serde::Deserialize; use tracing::{debug, warn}; @@ -40,11 +41,13 @@ fn find_in_parents( debug!("Checking parent role {}", parent); match role.as_ref().borrow().tasks.matches(user, command) { Ok(matches) => { + debug!("Parent role {} matched", parent); if !matcher.command_matching() || (matches.command_matching() && matches.score.cmd_cmp(&matcher.score) == Ordering::Less) { - matcher.score = matches.score; + debug!("Parent role {} is better", parent); + matcher.score.cmd_min = matches.score.cmd_min; matcher.settings = matches.settings; result = PluginResultAction::Edit; }