diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 835134b..0000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,122 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) - -## [3.0] - 2022-12-04 - -### Role Design - -In this Major version, conception of sr tool is redesigned. ancient versions of RootAsRole doesn't respect properly the RBAC model, or in other point of view, didn't respect least privilege principle. Privileges should be associated directly to command(s), not to a role, because a role is a set of permissions that needs different privileges. As example, considering that network administrator needs to access to CAP_NET_BIND_SERVICE to setup his network monitoring service, he doesn't need to edit network interfaces configuration with CAP_NET_ADMIN. So the role needs a first command which has CAP_NET_BIND_SERVICE to start monitoring service and a second command with CAP_NET_ADMIN for edit network configuration. In previous implementation, this configuration should need 2 different roles, which is not the meaning of RBAC model. - -In this new version, we focuses on usability and conception of the tool. Because sudo is simpler to use, we redesigned `sr` argument management, which is now minimal, for the happiness of lazy people. RootAsRole is now a more lazy tool than `sudo` because our tool needs only 2 characters, which means 2 times less than `sudo`. We know that `sudo` is used in majority of distributions, so to avoid to change habits, we tried to reproduce the default usage of this tool. We know that sudo provides more functionnalities than our tool. But we think that sudo tries to resolve overloaded amount of needs which became very hard to modify without incidents. - -As reminder, `sudo` doesn't respect any security model . With `sr` we tried to setup a Role based access control model which allows administrator to manage privileges granting in respect of least privilege management. We also know that capabilities tries to respect capability based security model by using similar words. But the design is not respecting this model. Contrary to `sudo`, RootAsRole doesn't permit to user to change his effective identity. - -Also, With this new version, many vulnerabilities were fixed. - -### Role-Management - -Role-management is entierly redesigned. Rust is coming into Linux Kernel ! To approve this initiative, we decided to learn this language and try to implement it as best we could do on this days. Don't be rough, we are beginners on this language. Also, Rust fullfill security needs for the project. That's why Rust will be encouraged in the future. -We thought that previous cli were not that easy to use. So we thought about a TUI to structure and design our tool. Yes, a cli version is still available. By doing this TUI, we learnt a lot about Rust language mechanisms. That could help us to create a better cli. - -### eBPF - -`capable` is now working on Debian ! Because Debian is using a different implementation structure of headers, we had difficulties to handle Ubuntu, Debian and other OS. -In next versions, we'll enhance eBPF with new ways of implementation and some exciting functionnalities! - -With these changes, RootAsRole, has taken initiatives to simplify the deployment of least privilege principle based on process criteria. - -## Added on 3.0 - -- Evironment Variables management -- Support for Arch Linux -- setUID and Multiple setGID -- Partial Order Comparison between roles ! Ghosted roles abolished ! -- Unit-Test with [Criterion Testing Framework](https://github.com/Snaipe/Criterion) - -## Changed on 3.0 - -- XML Document DTD and conceptual structure - -## Deleted on 3.0 - -- `sr_aux` program which was useless. -- old `role-manager` implementation. It wasn't working at all, and source code wasn't reusable. - -## [2.2] - 2019-09-27 - -This version is focused on sr command, no changes in capable command - -### Added on 2.2 - -- Improve "-i" option, as user-friendly as possible. Explain every possibilities to specific user. If you don't know if you can, then do "-i" - -### Modified - -- fix quotes and apostrophes in sr -- fix various bugs with unwanted strings -- Some Refactoring and optimisations - -## [2.1] - 2019-08-14 - -This version is focused on capable program, no changes on sr command. - -### Added on 2.1 - -- New algorithm capabilities detection for capable command, based on namespaces with recursive namespace creation detection. This algorithm will work for almost all cases and is much more optimized than 2.0 algorithm. -- Beginning of stack trace filtering for capable command, we want to remove the cap_sys_admin capability when _do_fork is in the stack will work only on kernel version 5.X, the program remains retro-compatible for 4.10 version - -### Modified on 2.1 - -- 2.0 algorithm has now the namespace retrieving -- fix big mistakes on 2.0 algorithm -- fix output options management -- fix hidden memory leaks on capable tool - -### Removed - -- raw option removed for production usage, because the raw log is stored on desktop until shutdown can slow down the system when used in long time, furthermore the raw option isn't pertinent. - -## [2.0] - 2019-06-04 - -### Added on 2.0 - -- New Tool called "capable". this tool can be used to resolve capabilities asked by a program, this can be run as daemon, or with command to test. -- sr is now print which role is used when start - -### Changed on 2.0 - -- option -c is optionnal but it's mandatory to precise the command in the configuration. -- Fix bugs and memory leaks from testing suite and sr - -## [1.1] - 2019-05-02 - -### Added on 1.1 - -- Ability to no longer specify a role for the command sr -- Adding tests for functionality without role -- Added a system test environment -- README for testing system -- Adding tests for the test environment -- Added optional parameter -v to get the RAR version -- Added Changelog file - -### Changed on 1.1 - -- Correction of syntactical faults in the main README -- Fixed DTD on capabilities (require at least one capability in a role) - -## [1.0] - 2018-07-28 - -### Added - -- sr command which uses capabilities and xml role system to replace `sudo` or any alternative. -- initial project - -[2.2]: https://github.com/SamerW/RootAsRole/compare/V2.1...V2.2 -[2.1]: https://github.com/SamerW/RootAsRole/compare/V2.0...V2.1 -[2.0]: https://github.com/SamerW/RootAsRole/compare/V1.1...V2.0 -[1.1]: https://github.com/SamerW/RootAsRole/compare/V1.0...V1.1 -[1.0]: https://github.com/SamerW/RootAsRole/releases/tag/V1.0 diff --git a/Cargo.toml b/Cargo.toml index 2365d4d..fc9616e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ members = ["xtask", "rar-common"] [package] name = "rootasrole" # The project version is managed on json file in resources/rootasrole.json -version = "3.0.1" +version = "3.0.2" rust-version = "1.76.0" authors = ["Eddie Billoir "] edition = "2021" @@ -51,45 +51,37 @@ unexpected_cfgs = { level = "allow", check-cfg = ['cfg(tarpaulin_include)'] } [build-dependencies] -reqwest = { version = "0.12.4", features = ["blocking", "json"] } -pcre2 = "0.2.4" -regex = "1.9.1" -serde = { version = "1.0.200" } -serde_json = "1.0.116" +serde_json = "1.0.128" toml = "0.8.13" [dependencies] rar-common = { path = "rar-common", version = "3.0.0", package = "rootasrole-core" } tracing = "0.1.40" tracing-subscriber = "0.3.18" -libc = "0.2.155" +libc = "0.2.159" strum = { version = "0.26.3", features = ["derive"] } semver = { version = "1.0.23", features = ["serde"] } -nix = { version = "0.29.0", features = ["user","process", "signal", "fs"] } -#sudoers-reader = { path = "sudoers-reader" } +nix = { version = "0.29.0", features = ["user","process", "signal", "fs"] } capctl = "0.2.4" pcre2 = { version = "0.2.7", optional = true } -serde = { version = "1.0.202", features=["rc"] } -serde_json = "1.0.117" +serde = { version = "1.0.210", features=["rc"] } +serde_json = "1.0.128" ciborium = "0.2.2" glob = "0.3.1" pam-client = { version = "0.5.0", git = "https://gitlab.com/LeChatP/rust-pam-client.git" } -pam-sys = "1.0.0-alpha5" -bitflags = { version = "2.5.0" } +pam-sys = { version = "1.0.0-alpha.5", git = "https://github.com/LeChatP/pam-sys.git" } +bitflags = { version = "2.6.0" } shell-words = "1.1.0" syslog-tracing = "0.3.0" linked_hash_set = { version = "0.1.4" } derivative = "2.2.0" sha2 = "0.10.8" -sha1 = "0.10.6" -#md5 = "0.7.0" chrono = "0.4.38" pty-process = "0.4.0" -once_cell = "1.19.0" -pest = "2.7.8" -pest_derive = "2.7.8" -#phf = { version = "0.11.2", features = ["macros"] } -const_format = "0.2.32" +once_cell = "1.20.2" +pest = "2.7.14" +pest_derive = "2.7.14" +const_format = "0.2.33" hex = "0.4.3" [dev-dependencies] @@ -97,10 +89,6 @@ env_logger = "0.11.5" test-log = { version = "0.2.12", features = ["trace"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", default-features = false, features = ["env-filter", "fmt"] } -pest-test-gen = "0.1.7" -pest-test = "0.1.6" -lazy_static = "1.4.0" -toml = "0.8.19" [package.metadata.deb] @@ -121,6 +109,7 @@ preserve-symlinks = true conf-files = ["/etc/pam.d/sr", "/etc/security/rootasrole.json"] maintainer-scripts = "target/release/" extended-description = "RootAsRole is a project to allow Linux/Unix administrators to delegate their administrative tasks access rights to multiple co-administrators through RBAC model and Linux Capabilities features." +changelog = "target/debian/changelog" [package.metadata.generate-rpm] assets = [ diff --git a/README.md b/README.md index ffd4719..6fd6086 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ -# RootAsRole (V3.0.1) : A memory-safe and security-oriented alternative to sudo/su commands +# RootAsRole (V3.0.2) : A memory-safe and security-oriented alternative to sudo/su commands **RootAsRole** is a project to allow Linux/Unix administrators to delegate their administrative tasks access rights to users. Its main features are : diff --git a/rar-common/Cargo.toml b/rar-common/Cargo.toml index fe66a28..f5ef1b6 100644 --- a/rar-common/Cargo.toml +++ b/rar-common/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rootasrole-core" -version = "3.0.1" +version = "3.0.2" edition = "2021" description = "This core crate contains the RBAC and main features for the RootAsRole project." license = "GPL-3.0-or-later" @@ -8,7 +8,7 @@ license = "GPL-3.0-or-later" [dependencies] tracing = "0.1.40" tracing-subscriber = "0.3.18" -libc = "0.2.155" +libc = "0.2.159" strum = { version = "0.26.3", features = ["derive"] } semver = { version = "1.0.23", features = ["serde"] } nix = { version = "0.29.0", features = ["user","process", "signal", "fs"] } @@ -17,7 +17,6 @@ capctl = "0.2.4" pcre2 = { version = "0.2.7", optional = true } serde = { version = "1.0.202", features=["rc"] } serde_json = "1.0.117" -ciborium = "0.2.2" glob = { version = "0.3.1", optional = true } bitflags = { version = "2.5.0" } shell-words = "1.1.0" @@ -34,9 +33,6 @@ env_logger = "0.11.5" test-log = { version = "0.2.12", features = ["trace"] } tracing = "0.1.37" tracing-subscriber = { version = "0.3.16", default-features = false, features = ["env-filter", "fmt"] } -lazy_static = "1.4.0" -serde = { version = "1.0.202", features=["rc"] } -toml = "0.8.19" [features] pcre2 = ["dep:pcre2"] diff --git a/rar-common/src/util.rs b/rar-common/src/util.rs index 15b264d..da0524f 100644 --- a/rar-common/src/util.rs +++ b/rar-common/src/util.rs @@ -190,47 +190,52 @@ fn remove_outer_quotes(input: &str) -> String { pub fn parse_conf_command(command: &SCommand) -> Result, Box> { match command { - SCommand::Simple(command) => Ok(shell_words::split(command)?), - SCommand::Complex(command) => { - if let Some(array) = command.as_array() { - let mut result = Vec::new(); - if !array.iter().all(|item| { - // if it is a string - item.is_string() && { - //add to result - result.push(item.as_str().unwrap().to_string()); - true // continue - } - }) { - // if any of the items is not a string - return Err("Invalid command".into()); - } - Ok(result) - } else { - // call PluginManager - #[cfg(feature = "finder")] - { - let res = PluginManager::notify_complex_command_parser(command); - debug!("Parsed command {:?}", res); - res - } - #[cfg(not(feature = "finder"))] - { - Err("Invalid command".into()) - } - } - } + SCommand::Simple(command) => parse_simple_command(command), + SCommand::Complex(command) => parse_complex_command(command), } } -pub fn find_from_envpath

(exe_name: &P) -> Option -where - P: AsRef, -{ +fn parse_simple_command(command: &str) -> Result, Box> { + shell_words::split(command).map_err(Into::into) +} + +fn parse_complex_command(command: &serde_json::Value) -> Result, Box> { + if let Some(array) = command.as_array() { + let result: Result, _> = array + .iter() + .map(|item| { + item.as_str() + .map(|s| s.to_string()) + .ok_or_else(|| "Invalid command".into()) + }) + .collect(); + result + } else { + parse_complex_command_with_finder(command) + } +} + +#[cfg(feature = "finder")] +fn parse_complex_command_with_finder( + command: &serde_json::Value, +) -> Result, Box> { + let res = PluginManager::notify_complex_command_parser(command); + debug!("Parsed command {:?}", res); + res +} + +#[cfg(not(feature = "finder"))] +fn parse_complex_command_with_finder( + _command: &serde_json::Value, +) -> Result, Box> { + Err("Invalid command".into()) +} + +pub fn find_from_envpath>(exe_name: P) -> Option { env::var_os("PATH").and_then(|paths| { env::split_paths(&paths) .filter_map(|dir| { - let full_path = dir.join(exe_name); + let full_path = dir.join(&exe_name); if full_path.is_file() { Some(full_path) } else { @@ -241,20 +246,14 @@ where }) } -pub fn final_path(path: &String) -> PathBuf { - let result; - if let Some(env_path) = find_from_envpath(&path) { - result = env_path - } else if let Ok(cannon_path) = std::fs::canonicalize(path) { - result = cannon_path; +pub fn final_path(path: &str) -> PathBuf { + if let Some(env_path) = find_from_envpath(path) { + env_path + } else if let Ok(canon_path) = std::fs::canonicalize(path) { + canon_path } else { - result = path.parse().expect("The path is not valid"); + PathBuf::from(path) } - result - .to_str() - .expect("The path is not valid") - .parse() - .expect("The path is not valid") } #[cfg(debug_assertions)] diff --git a/rar-common/src/version.rs b/rar-common/src/version.rs index a54afb7..2a6bbc3 100644 --- a/rar-common/src/version.rs +++ b/rar-common/src/version.rs @@ -1,4 +1,4 @@ // This file is generated by build.rs // Do not edit this file directly // Instead edit build.rs and run cargo build -pub const PACKAGE_VERSION: &'static str = "3.0.1"; +pub const PACKAGE_VERSION: &str = "3.0.2"; diff --git a/resources/man/en_US.md b/resources/man/en_US.md index 66185f2..e1584a9 100644 --- a/resources/man/en_US.md +++ b/resources/man/en_US.md @@ -1,5 +1,5 @@ -% RootAsRole(8) System Manager's Manual -% Version 3.0.0 +% RootAsRole(8) RootAsRole 3.0.0 | System Manager's Manual +% Eddie Billoir % September 2024 # NAME @@ -84,9 +84,6 @@ For help, please visit or . -# AUTHOR -This manual was written by Eddie BILLOIR - # LICENSE GPLv3+: GNU GPL version 3 or later . diff --git a/resources/man/fr_FR.md b/resources/man/fr_FR.md index 675044a..41edb8e 100644 --- a/resources/man/fr_FR.md +++ b/resources/man/fr_FR.md @@ -1,5 +1,5 @@ -% RootAsRole(8) Manuel de l'administrateur système -% Version 3.0.0 +% RootAsRole(8) RootAsRole 3.0.0 | Manuel de l'administrateur système +% % Septembre 2024 # NAME diff --git a/src/chsr/cli/process.rs b/src/chsr/cli/process.rs index d681291..cf630df 100644 --- a/src/chsr/cli/process.rs +++ b/src/chsr/cli/process.rs @@ -392,41 +392,43 @@ pub fn process_input(storage: &Storage, inputs: Inputs) -> Result Err("Unknown Input".into()), } } - pub fn perform_on_target_opt( rconfig: &Rc>, role_id: Option, task_id: Option, exec_on_opt: impl Fn(Rc>) -> Result<(), Box>, ) -> Result<(), Box> { - let config = rconfig.as_ref().borrow_mut(); - if let Some(role_id) = role_id { - if let Some(role) = config.role(&role_id) { - if let Some(task_id) = task_id { - if let Some(task) = role.as_ref().borrow().task(&task_id) { - if let Some(options) = &task.as_ref().borrow_mut().options { - exec_on_opt(options.clone()) - } else { - let options = Rc::new(RefCell::new(Opt::default())); - let ret = exec_on_opt(options.clone()); - task.as_ref().borrow_mut().options = Some(options); - ret - } - } else { - Err("Task not found".into()) - } - } else if let Some(options) = &role.as_ref().borrow_mut().options { - exec_on_opt(options.clone()) - } else { - let options = Rc::new(RefCell::new(Opt::default())); - let ret = exec_on_opt(options.clone()); - role.as_ref().borrow_mut().options = Some(options); - ret - } + let mut config = rconfig.as_ref().borrow_mut(); + + // Helper function to execute on option or create a new one + fn execute_or_create_option( + exec_on_opt: impl Fn(Rc>) -> Result<(), Box>, + options: &mut Option>>, + ) -> Result<(), Box> { + if let Some(opt) = options { + exec_on_opt(opt.clone()) } else { - Err("Role not found".into()) + let new_opt = Rc::new(RefCell::new(Opt::default())); + let result = exec_on_opt(new_opt.clone()); + *options = Some(new_opt); + result } - } else { - return exec_on_opt(config.options.as_ref().unwrap().clone()); } + + // If role_id is provided, find the role + if let Some(role_id) = role_id { + let role = config.role(&role_id).ok_or("Role not found")?; + let mut role_borrowed = role.as_ref().borrow_mut(); + + // If task_id is provided, find the task + if let Some(task_id) = task_id { + let task = role_borrowed.task(&task_id).ok_or("Task not found")?; + return execute_or_create_option(exec_on_opt, &mut task.as_ref().borrow_mut().options); + } + // No task_id, use role options + return execute_or_create_option(exec_on_opt, &mut role_borrowed.options); + } + + // No role_id, use global config options + execute_or_create_option(exec_on_opt, &mut config.options) } diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 9b56b75..af77b08 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "xtask" # The project version is managed on json file in resources/rootasrole.json -version = "3.0.1" +version = "3.0.2" edition = "2021" publish = false diff --git a/xtask/src/deploy/debian.rs b/xtask/src/deploy/debian.rs index 88574db..094d22f 100644 --- a/xtask/src/deploy/debian.rs +++ b/xtask/src/deploy/debian.rs @@ -1,6 +1,11 @@ -use std::process::{Command, ExitStatus}; +use std::{ + fs::File, + io::{BufRead, Write}, + process::{Command, ExitStatus}, +}; use anyhow::Context; +use tracing::debug; use crate::{ installer::{self, dependencies::install_dependencies, InstallDependenciesOptions, Profile}, @@ -19,6 +24,50 @@ fn dependencies(os: &OsTarget, priv_bin: Option) -> Result Result<(), anyhow::Error> { + let changelog_path = "target/debian/changelog"; + if std::path::Path::new(changelog_path).exists() { + return Ok(()); + } + let binding = Command::new("git") + .args(["tag", "--sort=-creatordate"]) + .output()?; + let mut ordered_tags = binding.stdout.lines(); + + let from = ordered_tags + .next() + .expect("Are you in the git repository ?")?; + + let to = ordered_tags + .next() + .expect("Are you in the git repository ?")?; + + debug!("Generating changelog from {} to {}", from, to); + + let changes = Command::new("git") + .args(["log", "--pretty=format: %s", &format!("{}..{}", to, from)]) + .output()?; + debug!( + "Changes: {}", + String::from_utf8(changes.stdout.clone()).unwrap() + ); + let changelog = format!( + r#"rootasrole ({version}) {dist}; urgency={urgency} +{changes} + + -- Eddie Billoir {date} +"#, + version = env!("CARGO_PKG_VERSION"), + dist = "unstable", + urgency = "low", + changes = String::from_utf8(changes.stdout).unwrap(), + date = chrono::Local::now().format("%a, %d %b %Y %T %z") + ); + File::create(changelog_path)?.write_all(changelog.as_bytes())?; + + Ok(()) +} + pub fn make_deb( os: Option, profile: Profile, @@ -41,6 +90,7 @@ pub fn make_deb( privbin: priv_bin, })?; setup_maint_scripts()?; + generate_changelog()?; Command::new("cargo") .arg("deb")