diff --git a/.github/workflows/pkg.yml b/.github/workflows/pkg.yml new file mode 100644 index 00000000..b6e339ef --- /dev/null +++ b/.github/workflows/pkg.yml @@ -0,0 +1,37 @@ +name: Deploy pkg to GitHub Packages + +## only triger manual +on: + workflow_dispatch: + +jobs: + deploy: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Rust + uses: actions-rs/toolchain@v1 + with: + profile: minimal + toolchain: stable + components: clippy + override: true + + - name: Install Dependencies + run: ./dependencies.sh -yd + + - name: Configure + run: sudo ./configure.sh -yd + + - name: Install cargo deb + run: cargo install cargo-deb + + - name: Build + run: cargo deb + + - name: Upload to GitHub \ No newline at end of file diff --git a/Cargo.toml b/Cargo.toml index b561146c..2bd01e76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["xtask", "capable", "capable-common"] +members = ["xtask", "capable", "capable-common", "rar-common"] [package] name = "RootAsRole" @@ -9,11 +9,13 @@ rust-version = "1.74.1" authors = ["Eddie Billoir "] edition = "2021" default-run = "sr" -description = "RootAsRole is an alternative to sudo that uses Linux capabilities and RBAC for scalability." -license-file = "LICENSE" +description = "An alternative to sudo that uses Linux capabilities and Role based access control." +license = "GPL-3.0-or-later" repository = "https://github.com/LeChatP/RootAsRole" +homepage = "https://lechatp.github.io/RootAsRole/" keywords = ["sudo", "capabilities", "rbac", "linux", "security"] categories = ["command-line-utilities", "os::linux-apis", "config"] +exclude = ["sudoers-reader/*", "book/*"] [badges] maintainance ={ status = "actively-maintained", badge = "https://img.shields.io/badge/maintenance-actively%20maintained-brightgreen.svg" } @@ -46,12 +48,13 @@ serde_json = "1.0.116" toml = "0.8.13" [dependencies] +rar-common = { path = "rar-common" } tracing = "0.1.40" tracing-subscriber = "0.3.18" libc = "0.2.155" -strum = { version = "0.26.2", features = ["derive"] } +strum = { version = "0.26.3", features = ["derive"] } semver = { version = "1.0.23", features = ["serde"] } -nix = { version = "0.28.0", features = ["user","process", "signal", "fs"] } +nix = { version = "0.29.0", features = ["user","process", "signal", "fs"] } #sudoers-reader = { path = "sudoers-reader" } capctl = "0.2.4" pcre2 = "0.2.7" @@ -86,3 +89,44 @@ tracing-subscriber = { version = "0.3.16", default-features = false, features = pest-test-gen = "0.1.7" pest-test = "0.1.6" lazy_static = "1.4.0" + + +[package.metadata.deb] +maintainer = "Eddie Billoir " +license-file = "LICENSE" +depends = "libpam0g, e2fsprogs, libcap2-bin, libpam-modules, libpcre2-8-0" +section = "admin" +priority = "optional" +assets = [ + ["target/release/sr", "usr/bin/sr", "0555"], + ["target/release/chsr", "usr/bin/chsr", "0555"], + ["resources/rootasrole.json", "usr/share/rootasrole/default.json", "0640"], + ["resources/debian/deb_sr_pam.conf", "usr/share/rootasrole/pam_sr.conf", "0644"] +] +conf-files = ["/etc/pam.d/sr"] +maintainer-scripts = "resources/debian/" +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." + +[package.metadata.generate-rpm] +assets = [ + { source = "target/release/sr", target = "/usr/bin/sr", mode = "0555" }, + { source = "target/release/chsr", target = "/usr/bin/chsr", mode = "0555" }, + { source = "resources/rootasrole.json", target = "/etc/security/rootasrole.json", mode = "0640" } +] + +[package.metadata.generate-rpm.requires] +libcap = "*" +e2fsprogs = "*" +coreutils = "*" +gawk = "*" +sed = "*" + +[package.metadata.aur] +depends = ["libcap", "e2fsprogs", "pcre2", "pam"] +files = [ ["target/release/sr", "/usr/bin/sr"], + ["target/release/chsr", "/usr/bin/chsr"], + ["resources/arch_sr_pam.conf", "/usr/share/rootasrole/pam_sr.conf"], + ["resources/rootasrole.json", "/usr/share/rootasrole/default.json"], + ["resources/debian/postinst", "/usr/share/rootasrole/postinst" ] ] +custom = [ "$pkgdir/usr/share/rootasrole/postinst" ] + diff --git a/build.rs b/build.rs index aa7b7a7e..ebb71470 100644 --- a/build.rs +++ b/build.rs @@ -38,6 +38,22 @@ fn set_cargo_version(package_version: &str, file: &str) -> Result<(), Box Result<(), Box> { + let pkgbuild = File::open(std::path::Path::new(file)).expect("PKGBUILD not found"); + let reader = BufReader::new(pkgbuild); + let lines = reader.lines().map(|l| l.unwrap()).collect::>(); + let mut pkgbuild = File::create(std::path::Path::new(file)).expect("PKGBUILD not found"); + for line in lines { + if line.starts_with("pkgver") { + writeln!(pkgbuild, "pkgver={}", package_version)?; + } else { + writeln!(pkgbuild, "{}", line)?; + } + } + pkgbuild.sync_all()?; + Ok(()) +} + fn write_doc(f: &mut File) -> Result<(), Box> { let docresp = reqwest::blocking::get( "https://git.kernel.org/pub/scm/docs/man-pages/man-pages.git/plain/man7/capabilities.7", @@ -171,4 +187,6 @@ fn main() { // } f.flush().unwrap(); + + } diff --git a/makepkg.sh b/makepkg.sh new file mode 100755 index 00000000..ad9b22d2 --- /dev/null +++ b/makepkg.sh @@ -0,0 +1,26 @@ +#/bin/sh + +# This script build every package for Arch Linux, Debian, Fedora. + +cargo build --release --bin sr --bin chsr || exit 1 + +# Arch Linux +if [ -z "$ARCH" ]; then + ARCH=$(uname -m) +fi +PKGEXT=.pkg.tar.zst + + +mkdir -p target/arch/usr/bin +mkdir -p target/arch/etc/pam.d +mkdir -p target/arch/usr/share/rootasrole +cp target/release/sr target/release/chsr target/arch/usr/bin +cp resources/rootasrole.json target/arch/usr/share/rootasrole/default.json +cp resources/arch/arch_sr_pam.conf target/arch/etc/pam.d/sr +cp resources/arch/PKGBUILD resources/arch/rootasrole.install target/arch + +sed -i "s/%ARCH%/$ARCH/g" target/arch/PKGBUILD + +cd target/arch + +makepkg -f -p PKGBUILD diff --git a/rar-common/Cargo.toml b/rar-common/Cargo.toml new file mode 100644 index 00000000..62a653f0 --- /dev/null +++ b/rar-common/Cargo.toml @@ -0,0 +1,46 @@ +[package] +name = "rar-common" +version = "0.1.0" +edition = "2021" + +[dependencies] +tracing = "0.1.40" +tracing-subscriber = "0.3.18" +libc = "0.2.155" +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" } +capctl = "0.2.4" +pcre2 = "0.2.7" +serde = { version = "1.0.202", features=["rc"] } +serde_json = "1.0.117" +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" } +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.37" +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" +hex = "0.4.3" + +[dev-dependencies] +env_logger = "*" +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" \ No newline at end of file diff --git a/src/api.rs b/rar-common/src/api.rs similarity index 98% rename from src/api.rs rename to rar-common/src/api.rs index 05d6a5c1..a1ff9ae8 100644 --- a/src/api.rs +++ b/rar-common/src/api.rs @@ -6,9 +6,9 @@ use serde_json::Value; use strum::EnumIs; use tracing::debug; -use crate::common::database::finder::{Cred, ExecSettings, TaskMatch, UserMin}; +use crate::database::finder::{Cred, ExecSettings, TaskMatch, UserMin}; -use super::database::{ +use crate::database::{ finder::FilterMatcher, structs::{SActor, SConfig, SRole, STask}, }; diff --git a/rar-common/src/config.rs b/rar-common/src/config.rs new file mode 100644 index 00000000..e69de29b diff --git a/src/database/finder.rs b/rar-common/src/database/finder.rs similarity index 99% rename from src/database/finder.rs rename to rar-common/src/database/finder.rs index 8446d963..db4eb3e3 100644 --- a/src/database/finder.rs +++ b/rar-common/src/database/finder.rs @@ -17,20 +17,15 @@ use pcre2::bytes::RegexBuilder; use strum::EnumIs; use tracing::{debug, warn}; -use crate::{ - as_borrow, - common::{ - api::{PluginManager, PluginResultAction}, - database::{ - options::{Opt, OptStack}, - structs::{ - SActor, SActorType, SCommand, SCommands, SConfig, SGroups, SRole, STask, - SetBehavior, - }, +use crate::{api::{PluginManager, PluginResultAction}, as_borrow}; +use crate::database::{ + options::{Opt, OptStack}, + structs::{ + SActor, SActorType, SCommand, SCommands, SConfig, SGroups, SRole, STask, + SetBehavior, }, - util::capabilities_are_exploitable, - }, -}; + }; +use crate::util::capabilities_are_exploitable; use bitflags::bitflags; #[derive(Debug, PartialEq, Eq, Clone, Copy)] @@ -1000,11 +995,11 @@ mod tests { use test_log::test; use crate::{ - common::database::{ + database::{ make_weak_config, options::{EnvBehavior, PathBehavior, SAuthentication, SBounding, SPrivileged}, structs::IdTask, - version::Versioning, + versionning::Versioning, }, rc_refcell, }; diff --git a/src/database/migration.rs b/rar-common/src/database/migration.rs similarity index 99% rename from src/database/migration.rs rename to rar-common/src/database/migration.rs index 04e044c7..02dc1811 100644 --- a/src/database/migration.rs +++ b/rar-common/src/database/migration.rs @@ -3,7 +3,7 @@ use std::error::Error; use semver::Version; use tracing::debug; -use crate::common::version::PACKAGE_VERSION; +use crate::version::PACKAGE_VERSION; pub struct Migration { pub from: fn() -> Version, diff --git a/src/database/mod.rs b/rar-common/src/database/mod.rs similarity index 93% rename from src/database/mod.rs rename to rar-common/src/database/mod.rs index 8806a98b..ae90b31a 100644 --- a/src/database/mod.rs +++ b/rar-common/src/database/mod.rs @@ -1,30 +1,29 @@ use std::{cell::RefCell, error::Error, rc::Rc}; -use crate::common::config::save_settings; -use crate::common::util::{toggle_lock_config, ImmutableLock}; -use crate::common::version::PACKAGE_VERSION; +use crate::save_settings; +use crate::util::{toggle_lock_config, ImmutableLock}; +use crate::version::PACKAGE_VERSION; use chrono::Duration; use linked_hash_set::LinkedHashSet; use serde::{de, Deserialize, Serialize}; use tracing::debug; -use self::{migration::Migration, options::EnvKey, structs::SConfig, version::Versioning}; +use self::{migration::Migration, options::EnvKey, structs::SConfig, versionning::Versioning}; -use super::config::SettingsFile; -use super::util::warn_if_mutable; -use super::{ - config::{RemoteStorageSettings, ROOTASROLE}, - immutable_effective, - util::parse_capset_iter, +use crate::SettingsFile; +use crate::util::warn_if_mutable; +use crate::{ + RemoteStorageSettings, ROOTASROLE, + util::{parse_capset_iter, immutable_effective}, }; -use super::{open_with_privileges, write_json_config}; +use crate::{open_with_privileges, write_json_config}; pub mod finder; pub mod migration; pub mod options; pub mod structs; -pub mod version; +pub mod versionning; pub mod wrapper; pub fn make_weak_config(config: &Rc>) { @@ -72,7 +71,7 @@ pub fn read_json_config( if Migration::migrate( &versionned_config.version, &mut *config.as_ref().borrow_mut(), - version::JSON_MIGRATIONS, + versionning::JSON_MIGRATIONS, )? { save_json(settings.clone(), config.clone())?; } diff --git a/src/database/options.rs b/rar-common/src/database/options.rs similarity index 99% rename from src/database/options.rs rename to rar-common/src/database/options.rs index bc99f4e2..8c03730e 100644 --- a/src/database/options.rs +++ b/rar-common/src/database/options.rs @@ -1181,9 +1181,9 @@ mod tests { use nix::unistd::User; use crate::as_borrow_mut; - use crate::common::database::wrapper::SConfigWrapper; - use crate::common::database::wrapper::SRoleWrapper; - use crate::common::database::wrapper::STaskWrapper; + use crate::database::wrapper::SConfigWrapper; + use crate::database::wrapper::SRoleWrapper; + use crate::database::wrapper::STaskWrapper; use crate::rc_refcell; use super::super::options::*; diff --git a/src/database/structs.rs b/rar-common/src/database/structs.rs similarity index 99% rename from src/database/structs.rs rename to rar-common/src/database/structs.rs index 61489c6f..dbed1c8e 100644 --- a/src/database/structs.rs +++ b/rar-common/src/database/structs.rs @@ -20,9 +20,8 @@ use std::{ rc::{Rc, Weak}, }; -use crate::common::database::is_default; - use super::{ + is_default, options::Opt, wrapper::{OptWrapper, STaskWrapper}, }; @@ -684,7 +683,7 @@ mod tests { use crate::{ as_borrow, - common::database::options::{EnvBehavior, PathBehavior, SAuthentication, TimestampType}, + database::options::{EnvBehavior, PathBehavior, SAuthentication, TimestampType}, }; use super::*; diff --git a/src/database/version.rs b/rar-common/src/database/versionning.rs similarity index 94% rename from src/database/version.rs rename to rar-common/src/database/versionning.rs index adc59736..d13182e2 100644 --- a/src/database/version.rs +++ b/rar-common/src/database/versionning.rs @@ -3,8 +3,8 @@ use serde::{Deserialize, Serialize}; use std::fmt::Debug; use super::migration::Migration; -use crate::common::config::SettingsFile; -use crate::common::version; +use crate::SettingsFile; +use crate::version; use super::structs::*; diff --git a/src/database/wrapper.rs b/rar-common/src/database/wrapper.rs similarity index 100% rename from src/database/wrapper.rs rename to rar-common/src/database/wrapper.rs diff --git a/src/config.rs b/rar-common/src/lib.rs similarity index 96% rename from src/config.rs rename to rar-common/src/lib.rs index a0532a77..10c03bc0 100644 --- a/src/config.rs +++ b/rar-common/src/lib.rs @@ -57,19 +57,23 @@ use std::{cell::RefCell, error::Error, ffi::OsStr, path::PathBuf, rc::Rc}; use serde::{Deserialize, Serialize}; use tracing::debug; -use crate::{ - common::{ +pub mod util; +pub mod database; +pub mod api; +pub mod version; +pub mod plugin; + + +use util::{ dac_override_effective, open_with_privileges, read_effective, - util::{toggle_lock_config, ImmutableLock}, - write_json_config, - }, - rc_refcell, + toggle_lock_config, ImmutableLock, + write_json_config, }; -use super::database::{ +use database::{ migration::Migration, structs::SConfig, - version::{self, Versioning}, + versionning::{Versioning, JSON_MIGRATIONS, SETTINGS_MIGRATIONS}, }; #[derive(Serialize, Deserialize, Debug, Clone)] @@ -257,7 +261,7 @@ where if Migration::migrate( &value.version, &mut *settingsfile.as_ref().borrow_mut(), - version::SETTINGS_MIGRATIONS, + SETTINGS_MIGRATIONS, )? { Migration::migrate( &value.version, @@ -267,7 +271,7 @@ where .config .as_ref() .borrow_mut(), - version::JSON_MIGRATIONS, + JSON_MIGRATIONS, )?; save_settings(settingsfile.clone())?; } diff --git a/src/plugin/hashchecker.rs b/rar-common/src/plugin/hashchecker.rs similarity index 99% rename from src/plugin/hashchecker.rs rename to rar-common/src/plugin/hashchecker.rs index 896f52dc..52cef494 100644 --- a/src/plugin/hashchecker.rs +++ b/rar-common/src/plugin/hashchecker.rs @@ -4,7 +4,7 @@ use nix::unistd::{access, AccessFlags}; use serde::{Deserialize, Serialize}; use tracing::{debug, warn}; -use crate::common::{ +use crate::{ api::PluginManager, database::{ finder::{final_path, parse_conf_command}, @@ -128,7 +128,7 @@ mod tests { use super::*; use crate::{ - common::database::{ + database::{ finder::{Cred, TaskMatcher}, structs::{IdTask, SActor, SCommand, SCommands, SConfig, SRole, STask}, }, diff --git a/src/plugin/hierarchy.rs b/rar-common/src/plugin/hierarchy.rs similarity index 99% rename from src/plugin/hierarchy.rs rename to rar-common/src/plugin/hierarchy.rs index 8fa3f0f9..352de2a3 100644 --- a/src/plugin/hierarchy.rs +++ b/rar-common/src/plugin/hierarchy.rs @@ -1,6 +1,6 @@ use std::cmp::Ordering; -use crate::common::{ +use crate::{ api::{PluginManager, PluginResultAction}, database::{ finder::{Cred, FilterMatcher, TaskMatch, TaskMatcher}, @@ -81,7 +81,7 @@ mod tests { use super::*; use crate::{ - common::database::{ + database::{ finder::UserMin, structs::{IdTask, SActor, SCommand, SCommands, SConfig, STask}, }, diff --git a/src/plugin/mod.rs b/rar-common/src/plugin/mod.rs similarity index 77% rename from src/plugin/mod.rs rename to rar-common/src/plugin/mod.rs index f1372d5f..e5384329 100644 --- a/src/plugin/mod.rs +++ b/rar-common/src/plugin/mod.rs @@ -2,7 +2,7 @@ mod hashchecker; mod hierarchy; mod ssd; -pub(crate) fn register_plugins() { +pub fn register_plugins() { hashchecker::register(); ssd::register(); hierarchy::register(); diff --git a/src/plugin/ssd.rs b/rar-common/src/plugin/ssd.rs similarity index 96% rename from src/plugin/ssd.rs rename to rar-common/src/plugin/ssd.rs index 687e030b..8ffe10ea 100644 --- a/src/plugin/ssd.rs +++ b/rar-common/src/plugin/ssd.rs @@ -6,12 +6,10 @@ use serde_json::Error; use crate::{ as_borrow, - common::{ - api::{PluginManager, PluginResult}, - database::{ - finder::Cred, - structs::{SActor, SConfig, SGroups, SRole}, - }, + api::{PluginManager, PluginResult}, + database::{ + finder::Cred, + structs::{SActor, SConfig, SGroups, SRole}, }, }; @@ -146,7 +144,7 @@ mod tests { use super::*; use crate::{ - common::database::structs::{SActor, SConfig, SRole}, + database::structs::{SActor, SConfig, SRole}, rc_refcell, }; use nix::unistd::{Group, Pid}; diff --git a/src/util.rs b/rar-common/src/util.rs similarity index 64% rename from src/util.rs rename to rar-common/src/util.rs index 11ea73a4..af610cb9 100644 --- a/src/util.rs +++ b/rar-common/src/util.rs @@ -1,20 +1,20 @@ -use std::{error::Error, fs::File, os::fd::AsRawFd, path::PathBuf}; +use std::{error::Error, fs::File, os::fd::AsRawFd, path::PathBuf, ffi::CString, path::Path}; use capctl::{Cap, CapSet, ParseCapError}; use libc::{FS_IOC_GETFLAGS, FS_IOC_SETFLAGS}; use strum::EnumIs; -use tracing::{debug, warn}; +use tracing::{debug, warn, Level}; +use capctl::{prctl, CapState}; +use serde::Serialize; +use tracing_subscriber::util::SubscriberInitExt; -use crate::common::{ - dac_override_effective, fowner_effective, immutable_effective, open_with_privileges, - read_effective, -}; pub const RST: &str = "\x1B[0m"; pub const BOLD: &str = "\x1B[1m"; pub const UNDERLINE: &str = "\x1B[4m"; pub const RED: &str = "\x1B[31m"; + #[macro_export] macro_rules! upweak { ($e:expr) => { @@ -162,12 +162,164 @@ fn remove_outer_quotes(input: &str) -> String { } } +#[cfg(debug_assertions)] +pub fn subsribe(tool: &str) { + use std::io; + let identity = CString::new(tool).unwrap(); + let options = syslog_tracing::Options::LOG_PID; + let facility = syslog_tracing::Facility::Auth; + let _syslog = syslog_tracing::Syslog::new(identity, options, facility).unwrap(); + tracing_subscriber::fmt() + .with_max_level(Level::DEBUG) + .with_file(true) + .with_line_number(true) + .with_writer(io::stdout) + .finish() + .init(); +} + +#[cfg(not(debug_assertions))] +pub fn subsribe(tool: &str) { + use std::panic::set_hook; + + let identity = CString::new(tool).unwrap(); + let options = syslog_tracing::Options::LOG_PID; + let facility = syslog_tracing::Facility::Auth; + let syslog = syslog_tracing::Syslog::new(identity, options, facility).unwrap(); + tracing_subscriber::fmt() + .compact() + .with_max_level(Level::WARN) + .with_file(false) + .with_timer(false) + .with_line_number(false) + .with_target(false) + .without_time() + .with_writer(syslog) + .finish() + .init(); + set_hook(Box::new(|info| { + if let Some(s) = info.payload().downcast_ref::() { + println!("{}", s); + } + })); +} + +pub fn drop_effective() -> Result<(), capctl::Error> { + let mut current = CapState::get_current()?; + current.effective.clear(); + current.set_current() +} + +pub fn cap_effective(cap: Cap, enable: bool) -> Result<(), capctl::Error> { + let mut current = CapState::get_current()?; + current.effective.set_state(cap, enable); + current.set_current() +} + +pub fn setpcap_effective(enable: bool) -> Result<(), capctl::Error> { + cap_effective(Cap::SETPCAP, enable) +} + +pub fn setuid_effective(enable: bool) -> Result<(), capctl::Error> { + cap_effective(Cap::SETUID, enable) +} + +pub fn setgid_effective(enable: bool) -> Result<(), capctl::Error> { + cap_effective(Cap::SETGID, enable) +} + +pub fn fowner_effective(enable: bool) -> Result<(), capctl::Error> { + cap_effective(Cap::FOWNER, enable) +} + +pub fn read_effective(enable: bool) -> Result<(), capctl::Error> { + cap_effective(Cap::DAC_READ_SEARCH, enable) +} + +pub fn dac_override_effective(enable: bool) -> Result<(), capctl::Error> { + cap_effective(Cap::DAC_OVERRIDE, enable) +} + +pub fn immutable_effective(enable: bool) -> Result<(), capctl::Error> { + cap_effective(Cap::LINUX_IMMUTABLE, enable) +} + +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> +where + S: std::convert::AsRef + Clone, +{ + let file = create_with_privileges(path)?; + serde_json::to_writer_pretty(file, &settings)?; + Ok(()) +} + +pub fn create_with_privileges>(p: P) -> Result { + std::fs::File::create(&p).or_else(|e| { + debug!( + "Error creating file without privilege, trying with privileges: {}", + e + ); + dac_override_effective(true)?; + let res = std::fs::File::create(p).inspect_err(|e| { + debug!( + "Error creating file without privilege, trying with privileges: {}", + e + ); + }); + dac_override_effective(false)?; + res + }) +} + +pub fn open_with_privileges>(p: P) -> Result { + std::fs::File::open(&p).or_else(|e| { + debug!( + "Error creating file without privilege, trying with privileges: {}", + e + ); + read_effective(true).or(dac_override_effective(true))?; + let res = std::fs::File::open(p); + read_effective(false)?; + dac_override_effective(false)?; + res + }) +} + +pub fn remove_with_privileges>(p: P) -> Result<(), std::io::Error> { + std::fs::remove_file(&p).or_else(|e| { + debug!( + "Error creating file without privilege, trying with privileges: {}", + e + ); + dac_override_effective(true)?; + let res = std::fs::remove_file(p); + dac_override_effective(false)?; + res + }) +} + +pub fn create_dir_all_with_privileges>(p: P) -> Result<(), std::io::Error> { + std::fs::create_dir_all(&p).or_else(|e| { + debug!( + "Error creating file without privilege, trying with privileges: {}", + e + ); + dac_override_effective(true)?; + let res = std::fs::create_dir_all(p); + read_effective(false)?; + dac_override_effective(false)?; + res + }) +} + #[cfg(test)] mod test { use std::fs; - use capctl::CapState; - use super::*; #[test] diff --git a/rar-common/src/version.rs b/rar-common/src/version.rs new file mode 100644 index 00000000..9472607f --- /dev/null +++ b/rar-common/src/version.rs @@ -0,0 +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.0-alpha.5"; diff --git a/resources/arch/PKGBUILD b/resources/arch/PKGBUILD new file mode 100644 index 00000000..adce32ca --- /dev/null +++ b/resources/arch/PKGBUILD @@ -0,0 +1,43 @@ +# Maintainer: Eddie Billoir + +pkgname=rootasrole +pkgver=3.0.0_alpha.5 +pkgrel=1 +pkgdesc='Alternative to sudo to run some administrative commands that uses Linux capabilities and RBAC for scalability.' +url='https://lechatp.github.io/RootAsRole/' +license=('GPL-3.0-or-later') +arch=('x86_64') +source=("https://github.com/LeChatP/RootAsRole/archive/v${pkgver//_/-}.tar.gz") +sha256sums=('SKIP') +depends=('libcap' 'e2fsprogs' 'pcre2' 'pam') +backup=('etc/pam.d/sr' 'etc/security/rootasrole.json') +validpgpkeys=('74F43C5774BE1F3527DEFA4835C155EA0525104D') +makedepends=(cargo) +source=("$pkgname-$pkgver.tar.gz::https://static.crates.io/crates/$pkgname/$pkgname-$pkgver.crate") +#source=('https://github.com/LeChatP/RootAsRole/releases/download/v${pkgver//_/-}/RootAsRole-${pkgver//_/-}-$arch.tar.gz') +install=rootasrole.install + +prepare() { + export RUSTUP_TOOLCHAIN=stable + cargo fetch --locked --target "$(rustc -vV | sed -n 's/host: //p')" +} + +build() { + export RUSTUP_TOOLCHAIN=stable + export CARGO_TARGET_DIR=target + cargo build --frozen --release --all-features +} + +check() { + export RUSTUP_TOOLCHAIN=stable + cargo test --frozen --all-features +} + +package() { + cd $pkgname-$pkgver + install -Dm755 'target/release/sr' -t "$pkgdir/usr/bin" + install -Dm755 'target/release/chsr' -t "$pkgdir/usr/bin" + install -Dm644 'resources/arch/arch_sr_pam.conf' -t "$pkgdir/etc/pam.d/sr" + install -Dm644 'resources/rootasrole.json' -t "$pkgdir/usr/share/rootasrole/default.json" + setcap '=p' "$pkgdir/usr/bin/sr" +} diff --git a/resources/arch_sr_pam.conf b/resources/arch/arch_sr_pam.conf similarity index 100% rename from resources/arch_sr_pam.conf rename to resources/arch/arch_sr_pam.conf diff --git a/resources/arch/rootasrole.install b/resources/arch/rootasrole.install new file mode 100644 index 00000000..b628359f --- /dev/null +++ b/resources/arch/rootasrole.install @@ -0,0 +1,41 @@ +TARGET_PATH="/etc/security/rootasrole.json" + +log() { + echo "RootAsRole: $1" +} + +post_install() { + if [ ! -f "$TARGET_PATH" ]; then + cp "/usr/share/rootasrole/default.json" "$TARGET_PATH" || log "Failed to copy the default configuration file to $TARGET_PATH" && exit 1 + else + log "The configuration file $TARGET_PATH already exists. Skipping the post-installation process." + return 0 + fi + + # Check the file system type + FS_TYPE=$(df -T "$TARGET_PATH" | awk 'NR==2 {print $2}') + + # Supported file systems for immutable flag + # It may not work on all file systems, but it is supported on the most common ones. + case "$FS_TYPE" in + ext2|ext3|ext4|xfs|btrfs|ocfs2|jfs|reiserfs) + if ! grep -q '"immutable": true' "$TARGET_PATH"; then + sed -i 's/"immutable": false/"immutable": true/' "$TARGET_PATH" + log "The file $TARGET_PATH is now immutable, and sr will check that immutable is enforced before executing." + fi + # Attempt to set the immutable flag + if ! chattr +i "$TARGET_PATH"; then + log "Failed to set the immutable flag on $TARGET_PATH" + sed -i 's/"immutable": true/"immutable": false/' "$TARGET_PATH" + sed -i "s;\"CAP_LINUX_IMMUTABLE\";;g" "$TARGET_PATH" + fi + ;; + *) + log "The file system $FS_TYPE does not support the immutable flag. Avoid checking the immutable flag during sr execution." + sed -i "s/\"immutable\": true/\"immutable\": false/g" "$TARGET_PATH" + sed -i "s;\"CAP_LINUX_IMMUTABLE\";;g" "$TARGET_PATH" + return 1 + ;; + esac +} + diff --git a/resources/deb_sr_pam.conf b/resources/debian/deb_sr_pam.conf similarity index 100% rename from resources/deb_sr_pam.conf rename to resources/debian/deb_sr_pam.conf diff --git a/resources/debian/postinst b/resources/debian/postinst new file mode 100644 index 00000000..bfd4ccbc --- /dev/null +++ b/resources/debian/postinst @@ -0,0 +1,63 @@ +#!/bin/sh +#DEBHELPER# +set -e + +log() { + echo "RootAsRole: $1" +} + + +configure() { + + TARGET_PATH="/etc/security/rootasrole.json" + + if [ ! -f "$TARGET_PATH" ]; then + cp "/usr/share/rootasrole/default.json" "$TARGET_PATH" || log "Failed to copy the default configuration file to $TARGET_PATH" && exit 1 + elif ! diff -q "/usr/share/rootasrole/default.json" "$TARGET_PATH" > /dev/null; then + return 0 + fi + + # Check the file system type + FS_TYPE=$(df -T "$TARGET_PATH" | awk 'NR==2 {print $2}') + + # Supported file systems for immutable flag + # It may not work on all file systems, but it is supported on the most common ones. + case "$FS_TYPE" in + ext2|ext3|ext4|xfs|btrfs|ocfs2|jfs|reiserfs) + if ! grep -q '"immutable": true' "$TARGET_PATH"; then + sed -i 's/"immutable": false/"immutable": true/' "$TARGET_PATH" + log "The file $TARGET_PATH is now immutable, and sr will check that immutable is enforced before executing." + fi + # Attempt to set the immutable flag + if ! chattr +i "$TARGET_PATH" > /dev/null 2>&1; then + log "Failed to set the immutable flag on $TARGET_PATH" + sed -i 's/"immutable": true/"immutable": false/' "$TARGET_PATH" + sed -i "s;\"CAP_LINUX_IMMUTABLE\";;g" "$TARGET_PATH" + fi + ;; + *) + log "The file system $FS_TYPE does not support the immutable flag. Avoid checking the immutable flag during sr execution." + sed -i "s/\"immutable\": true/\"immutable\": false/g" "$TARGET_PATH" + sed -i "s;\"CAP_LINUX_IMMUTABLE\";;g" "$TARGET_PATH" + exit 1 + ;; + esac +} + +setcap "=p" "/usr/bin/sr" || (log "Failed to set capabilities on /usr/bin/sr" && exit 1) + +case "$1" in + configure|abort-remove|abort-deconfigure) + configure + ;; + + abort-upgrade|upgrade|triggers-only|disappear) + exit 0 + ;; + + *) + log "postinst called with unknown argument \`$1'" + exit 1 + ;; +esac + diff --git a/resources/rh_sr_pam.conf b/resources/rh/rh_sr_pam.conf similarity index 100% rename from resources/rh_sr_pam.conf rename to resources/rh/rh_sr_pam.conf diff --git a/src/chsr/cli/data.rs b/src/chsr/cli/data.rs index 2cb3f9f2..b29480bd 100644 --- a/src/chsr/cli/data.rs +++ b/src/chsr/cli/data.rs @@ -4,7 +4,7 @@ use capctl::CapSet; use chrono::Duration; use linked_hash_set::LinkedHashSet; -use crate::common::database::{ +use rar_common::database::{ options::{ EnvBehavior, EnvKey, OptType, PathBehavior, SAuthentication, SBounding, SPrivileged, TimestampType, diff --git a/src/chsr/cli/mod.rs b/src/chsr/cli/mod.rs index c16300b1..6c65ee80 100644 --- a/src/chsr/cli/mod.rs +++ b/src/chsr/cli/mod.rs @@ -13,7 +13,8 @@ use process::process_input; use tracing::debug; use usage::print_usage; -use crate::{common::config::Storage, util::escape_parser_string_vec}; +use rar_common::Storage; +use crate::util::escape_parser_string_vec; pub fn main(storage: &Storage, args: I) -> Result> where @@ -40,19 +41,11 @@ where mod tests { use std::{io::Write, rc::Rc}; - use crate::common::{ - config, - database::{read_json_config, structs::SCredentials}, - remove_with_privileges, - }; - - use super::super::common::{ - config::{RemoteStorageSettings, SettingsFile, Storage, ROOTASROLE}, - database::{options::*, structs::*, version::Versioning}, + use rar_common::{ + database::{options::*, read_json_config, structs::{SCredentials, *}, versionning::Versioning}, get_settings, rc_refcell, util::remove_with_privileges, RemoteStorageSettings, SettingsFile, Storage, StorageMethod, ROOTASROLE }; use super::*; - use crate::rc_refcell; use capctl::Cap; use chrono::TimeDelta; use tracing::error; @@ -71,7 +64,7 @@ mod tests { let path = format!("{}.{}", ROOTASROLE, name); let mut file = std::fs::File::create(path.clone()).unwrap(); let mut settings = SettingsFile::default(); - settings.storage.method = config::StorageMethod::JSON; + settings.storage.method = StorageMethod::JSON; settings.storage.settings = Some(RemoteStorageSettings::default()); settings.storage.settings.as_mut().unwrap().path = Some(path.into()); settings.storage.settings.as_mut().unwrap().immutable = Some(false); @@ -223,7 +216,7 @@ mod tests { #[test] fn test_all_main() { setup("all_main"); - let settings = config::get_settings(&format!("{}.{}", ROOTASROLE, "all_main")) + let settings = get_settings(&format!("{}.{}", ROOTASROLE, "all_main")) .expect("Failed to get settings"); let config = read_json_config(settings.clone()).expect("Failed to read json"); assert!(main(&Storage::JSON(config.clone()), vec!["--help"],) @@ -262,7 +255,7 @@ mod tests { fn test_r_complete_show_actors() { setup("r_complete_show_actors"); let settings = - config::get_settings(&format!("{}.{}", ROOTASROLE, "r_complete_show_actors")) + get_settings(&format!("{}.{}", ROOTASROLE, "r_complete_show_actors")) .expect("Failed to get settings"); let config = read_json_config(settings.clone()).expect("Failed to read json"); assert!(main( @@ -314,7 +307,7 @@ mod tests { #[test] fn test_purge_tasks() { setup("purge_tasks"); - let settings = config::get_settings(&format!("{}.{}", ROOTASROLE, "purge_tasks")) + let settings = get_settings(&format!("{}.{}", ROOTASROLE, "purge_tasks")) .expect("Failed to get settings"); let config = read_json_config(settings.clone()).expect("Failed to read json"); assert!(main( @@ -333,7 +326,7 @@ mod tests { #[test] fn test_r_complete_purge_all() { setup("r_complete_purge_all"); - let settings = config::get_settings(&format!("{}.{}", ROOTASROLE, "r_complete_purge_all")) + let settings = get_settings(&format!("{}.{}", ROOTASROLE, "r_complete_purge_all")) .expect("Failed to get settings"); let config = read_json_config(settings.clone()).expect("Failed to read json"); assert!(main( @@ -352,7 +345,7 @@ mod tests { #[test] fn test_r_complete_grant_u_user1_g_group1_g_group2_group3() { setup("r_complete_grant_u_user1_g_group1_g_group2_group3"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_grant_u_user1_g_group1_g_group2_group3" )) @@ -415,7 +408,7 @@ mod tests { #[test] fn test_r_complete_task_t_complete_show_all() { setup("r_complete_task_t_complete_show_all"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_task_t_complete_show_all" )) @@ -470,7 +463,7 @@ mod tests { #[test] fn test_r_complete_task_t_complete_purge_cmd() { setup("r_complete_task_t_complete_purge_cmd"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_task_t_complete_purge_cmd" )) @@ -492,7 +485,7 @@ mod tests { #[test] fn test_r_complete_task_t_complete_purge_cred() { setup("r_complete_task_t_complete_purge_cred"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_task_t_complete_purge_cred" )) @@ -510,7 +503,7 @@ mod tests { }) .is_ok_and(|b| b)); debug!("====="); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_task_t_complete_purge_cred" )) @@ -552,7 +545,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_cmd_setpolicy_deny_all() { setup("r_complete_t_t_complete_cmd_setpolicy_deny_all"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_cmd_setpolicy_deny_all" )) @@ -582,7 +575,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_cmd_setpolicy_allow_all() { setup("r_complete_t_t_complete_cmd_setpolicy_allow_all"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_cmd_setpolicy_allow_all" )) @@ -612,7 +605,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_cmd_whitelist_add_super_command_with_spaces() { setup("r_complete_t_t_complete_cmd_whitelist_add_super_command_with_spaces"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_cmd_whitelist_add_super_command_with_spaces" )) @@ -674,7 +667,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_cmd_blacklist_del_super_command_with_spaces() { setup("r_complete_t_t_complete_cmd_blacklist_del_super_command_with_spaces"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_cmd_blacklist_del_super_command_with_spaces" )) @@ -715,7 +708,7 @@ mod tests { fn test_r_complete_t_t_complete_cred_set_caps_cap_dac_override_cap_sys_admin_cap_sys_boot_setuid_user1_setgid_group1_group2( ) { setup("r_complete_t_t_complete_cred_set_caps_cap_dac_override_cap_sys_admin_cap_sys_boot_setuid_user1_setgid_group1_group2"); - let settings = config::get_settings(&format!("{}.{}",ROOTASROLE,"r_complete_t_t_complete_cred_set_caps_cap_dac_override_cap_sys_admin_cap_sys_boot_setuid_user1_setgid_group1_group2")).expect("Failed to get settings"); + let settings = get_settings(&format!("{}.{}",ROOTASROLE,"r_complete_t_t_complete_cred_set_caps_cap_dac_override_cap_sys_admin_cap_sys_boot_setuid_user1_setgid_group1_group2")).expect("Failed to get settings"); let config = read_json_config(settings.clone()).expect("Failed to read json"); assert!(main(&Storage::JSON(config.clone()), "r complete t t_complete cred set --caps cap_dac_override,cap_sys_admin,cap_sys_boot --setuid user1 --setgid group1,group2".split(" "), ) @@ -823,7 +816,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_cred_caps_setpolicy_deny_all() { setup("r_complete_t_t_complete_cred_caps_setpolicy_deny_all"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_cred_caps_setpolicy_deny_all" )) @@ -856,7 +849,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_cred_caps_setpolicy_allow_all() { setup("r_complete_t_t_complete_cred_caps_setpolicy_allow_all"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_cred_caps_setpolicy_allow_all" )) @@ -890,7 +883,7 @@ mod tests { fn test_r_complete_t_t_complete_cred_caps_whitelist_add_cap_dac_override_cap_sys_admin_cap_sys_boot( ) { setup("r_complete_t_t_complete_cred_caps_whitelist_add_cap_dac_override_cap_sys_admin_cap_sys_boot"); - let settings = config::get_settings(&format!("{}.{}",ROOTASROLE,"r_complete_t_t_complete_cred_caps_whitelist_add_cap_dac_override_cap_sys_admin_cap_sys_boot")).expect("Failed to get settings"); + let settings = get_settings(&format!("{}.{}",ROOTASROLE,"r_complete_t_t_complete_cred_caps_whitelist_add_cap_dac_override_cap_sys_admin_cap_sys_boot")).expect("Failed to get settings"); let config = read_json_config(settings.clone()).expect("Failed to read json"); assert!(main(&Storage::JSON(config.clone()), "r complete t t_complete cred caps whitelist add cap_dac_override cap_sys_admin cap_sys_boot".split(" ")) .inspect_err(|e| { @@ -933,7 +926,7 @@ mod tests { fn test_r_complete_t_t_complete_cred_caps_blacklist_add_cap_dac_override_cap_sys_admin_cap_sys_boot( ) { setup("r_complete_t_t_complete_cred_caps_blacklist_add_cap_dac_override_cap_sys_admin_cap_sys_boot"); - let settings = config::get_settings(&format!("{}.{}",ROOTASROLE,"r_complete_t_t_complete_cred_caps_blacklist_add_cap_dac_override_cap_sys_admin_cap_sys_boot")).expect("Failed to get settings"); + let settings = get_settings(&format!("{}.{}",ROOTASROLE,"r_complete_t_t_complete_cred_caps_blacklist_add_cap_dac_override_cap_sys_admin_cap_sys_boot")).expect("Failed to get settings"); let config = read_json_config(settings.clone()).expect("Failed to read json"); assert!(main(&Storage::JSON(config.clone()), "r complete t t_complete cred caps blacklist add cap_dac_override cap_sys_admin cap_sys_boot".split(" "), ) @@ -1054,7 +1047,7 @@ mod tests { #[test] fn test_options_show_all() { setup("options_show_all"); - let settings = config::get_settings(&format!("{}.{}", ROOTASROLE, "options_show_all")) + let settings = get_settings(&format!("{}.{}", ROOTASROLE, "options_show_all")) .expect("Failed to get settings"); let config = read_json_config(settings.clone()).expect("Failed to read json"); assert!(main( @@ -1095,7 +1088,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_options_show_env() { setup("r_complete_t_t_complete_options_show_env"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_options_show_env" )) @@ -1161,7 +1154,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_path_setpolicy_delete_all() { setup("r_complete_t_t_complete_o_path_setpolicy_delete_all"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_path_setpolicy_delete_all" )) @@ -1196,7 +1189,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_path_setpolicy_keep_unsafe() { setup("r_complete_t_t_complete_o_path_setpolicy_keep_unsafe"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_path_setpolicy_keep_unsafe" )) @@ -1280,7 +1273,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_path_whitelist_add() { setup("r_complete_t_t_complete_o_path_whitelist_add"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_path_whitelist_add" )) @@ -1562,7 +1555,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_path_blacklist_purge() { setup("r_complete_t_t_complete_o_path_blacklist_purge"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_path_blacklist_purge" )) @@ -1584,7 +1577,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_keep_only_myvar_var2() { setup("r_complete_t_t_complete_o_env_keep_only_MYVAR_VAR2"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_keep_only_MYVAR_VAR2" )) @@ -1661,7 +1654,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_delete_only_myvar_var2() { setup("r_complete_t_t_complete_o_env_delete_only_MYVAR_VAR2"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_delete_only_MYVAR_VAR2" )) @@ -1738,7 +1731,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_set_myvar_value_var2_value2() { setup("r_complete_t_t_complete_o_env_set_MYVAR_value_VAR2_value2"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_set_MYVAR_value_VAR2_value2" )) @@ -1810,7 +1803,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_add_myvar_value_var2_value2() { setup("r_complete_t_t_complete_o_env_add_MYVAR_value_VAR2_value2"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_add_MYVAR_value_VAR2_value2" )) @@ -1989,7 +1982,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_setpolicy_delete_all() { setup("r_complete_t_t_complete_o_env_setpolicy_delete_all"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_setpolicy_delete_all" )) @@ -2026,7 +2019,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_setpolicy_keep_all() { setup("r_complete_t_t_complete_o_env_setpolicy_keep_all"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_setpolicy_keep_all" )) @@ -2063,7 +2056,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_setpolicy_inherit() { setup("r_complete_t_t_complete_o_env_setpolicy_inherit"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_setpolicy_inherit" )) @@ -2100,7 +2093,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_whitelist_add_myvar() { setup("r_complete_t_t_complete_o_env_whitelist_add_MYVAR"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_whitelist_add_MYVAR" )) @@ -2216,7 +2209,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_whitelist_purge() { setup("r_complete_t_t_complete_o_env_whitelist_purge"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_whitelist_purge" )) @@ -2251,7 +2244,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_blacklist_add_myvar() { setup("r_complete_t_t_complete_o_env_blacklist_add_MYVAR"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_blacklist_add_MYVAR" )) @@ -2310,7 +2303,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_blacklist_set_myvar() { setup("r_complete_t_t_complete_o_env_blacklist_set_MYVAR"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_blacklist_set_MYVAR" )) @@ -2361,7 +2354,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_blacklist_purge() { setup("r_complete_t_t_complete_o_env_blacklist_purge"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_blacklist_purge" )) @@ -2396,7 +2389,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_env_checklist_add_myvar() { setup("r_complete_t_t_complete_o_env_checklist_add_MYVAR"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_env_checklist_add_MYVAR" )) @@ -2522,7 +2515,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_root_privileged() { setup("r_complete_t_t_complete_o_root_privileged"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_root_privileged" )) @@ -2610,7 +2603,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_bounding_strict() { setup("r_complete_t_t_complete_o_bounding_strict"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_bounding_strict" )) @@ -2646,7 +2639,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_bounding_ignore() { setup("r_complete_t_t_complete_o_bounding_ignore"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_bounding_ignore" )) @@ -2682,7 +2675,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_bounding_inherit() { setup("r_complete_t_t_complete_o_bounding_inherit"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_bounding_inherit" )) @@ -2718,7 +2711,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_auth_skip() { setup("r_complete_t_t_complete_o_auth_skip"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_auth_skip" )) @@ -2806,7 +2799,7 @@ mod tests { #[test] fn test_r_complete_t_t_complete_o_wildcard_denied_set() { setup("r_complete_t_t_complete_o_wildcard_denied_set"); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_wildcard_denied_set" )) @@ -2890,7 +2883,7 @@ mod tests { "~" ); debug!("====="); - let settings = config::get_settings(&format!( + let settings = get_settings(&format!( "{}.{}", ROOTASROLE, "r_complete_t_t_complete_o_wildcard_denied_set" )) diff --git a/src/chsr/cli/pair.rs b/src/chsr/cli/pair.rs index 4d011027..5c58b508 100644 --- a/src/chsr/cli/pair.rs +++ b/src/chsr/cli/pair.rs @@ -6,15 +6,13 @@ use linked_hash_set::LinkedHashSet; use pest::iterators::Pair; use tracing::{debug, warn}; -use crate::{ - cli::data::{RoleType, TaskType}, - common::database::{ - options::{ - EnvBehavior, OptType, PathBehavior, SAuthentication, SBounding, SPrivileged, - TimestampType, - }, - structs::{IdTask, SActor, SActorType, SGroups, SetBehavior}, +use crate::cli::data::{RoleType, TaskType}; +use rar_common::database::{ + options::{ + EnvBehavior, OptType, PathBehavior, SAuthentication, SBounding, SPrivileged, + TimestampType, }, + structs::{IdTask, SActor, SActorType, SGroups, SetBehavior}, }; use super::data::*; @@ -423,13 +421,14 @@ mod test { data::RoleType, pair::{recurse_pair, Cli, InputAction, Rule}, }, - common::{ - database::structs::SActor, - util::{BOLD, RED, RST}, - }, util::underline, }; + use rar_common::{ + database::structs::SActor, + util::{BOLD, RED, RST}, + }; + use super::Inputs; fn make_args(args: &str) -> String { diff --git a/src/chsr/cli/process.rs b/src/chsr/cli/process.rs index f2efd11d..c75bbc54 100644 --- a/src/chsr/cli/process.rs +++ b/src/chsr/cli/process.rs @@ -6,8 +6,8 @@ use json::*; use tracing::debug; -use crate::common::{ - config::Storage, +use rar_common::{ + Storage, database::{ options::{Opt, OptType}, structs::IdTask, @@ -394,7 +394,7 @@ pub fn process_input(storage: &Storage, inputs: Inputs) -> Result>, + rconfig: &Rc>, role_id: Option, task_id: Option, exec_on_opt: impl Fn(Rc>) -> Result<(), Box>, diff --git a/src/chsr/cli/process/json.rs b/src/chsr/cli/process/json.rs index 1e344d24..36ff748e 100644 --- a/src/chsr/cli/process/json.rs +++ b/src/chsr/cli/process/json.rs @@ -9,22 +9,24 @@ use std::{ use linked_hash_set::LinkedHashSet; use tracing::debug; -use crate::{ - cli::data::{InputAction, RoleType, SetListType, TaskType, TimeoutOpt}, - common::database::{ - options::{ - EnvBehavior, EnvKey, Opt, OptStack, OptType, PathBehavior, SEnvOptions, SPathOptions, - STimeout, - }, - structs::{IdTask, SCapabilities, SCommand, SRole, STask}, +use crate::cli::data::{InputAction, RoleType, SetListType, TaskType, TimeoutOpt}; + +use rar_common::{ + database::{ + options::{ + EnvBehavior, EnvKey, Opt, OptStack, OptType, PathBehavior, SEnvOptions, SPathOptions, + STimeout, }, - rc_refcell, + structs::{IdTask, SCapabilities, SCommand, SRole, STask}, + +}, +rc_refcell, }; use super::perform_on_target_opt; pub fn list_json( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, options: bool, @@ -48,7 +50,7 @@ pub fn list_json( fn list_task( task_id: Option, - role: &Rc>, + role: &Rc>, options: bool, options_type: Option, task_type: Option, @@ -103,7 +105,7 @@ fn list_task( } fn print_task( - task: &std::rc::Rc>, + task: &std::rc::Rc>, task_type: TaskType, ) { match task_type { @@ -126,7 +128,7 @@ fn print_task( } fn print_role( - role: &std::rc::Rc>, + role: &std::rc::Rc>, role_type: &RoleType, ) { match role_type { @@ -149,7 +151,7 @@ fn print_role( } pub fn role_add_del( - rconfig: &Rc>, + rconfig: &Rc>, action: InputAction, role_id: String, role_type: Option, @@ -198,7 +200,7 @@ pub fn role_add_del( } pub fn task_add_del( - rconfig: &Rc>, + rconfig: &Rc>, role_id: String, action: InputAction, task_id: IdTask, @@ -271,10 +273,10 @@ pub fn task_add_del( } pub fn grant_revoke( - rconfig: &Rc>, + rconfig: &Rc>, role_id: String, action: InputAction, - mut actors: Vec, + mut actors: Vec, ) -> Result> { debug!("chsr role r1 grant|revoke"); let config = rconfig.as_ref().borrow_mut(); @@ -310,12 +312,12 @@ pub fn grant_revoke( } pub fn cred_set( - rconfig: &Rc>, + rconfig: &Rc>, 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"); let config = rconfig.as_ref().borrow_mut(); @@ -337,12 +339,12 @@ pub fn cred_set( } pub fn cred_unset( - rconfig: &Rc>, + rconfig: &Rc>, 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"); let config = rconfig.as_ref().borrow_mut(); @@ -370,7 +372,7 @@ pub fn cred_unset( } pub fn cred_caps( - rconfig: &Rc>, + rconfig: &Rc>, role_id: String, task_id: IdTask, setlist_type: SetListType, @@ -450,10 +452,10 @@ pub fn cred_caps( } pub fn cred_setpolicy( - rconfig: &Rc>, + rconfig: &Rc>, role_id: String, task_id: IdTask, - cred_policy: crate::common::database::structs::SetBehavior, + cred_policy: rar_common::database::structs::SetBehavior, ) -> Result> { debug!("chsr role r1 task t1 cred setpolicy"); let config = rconfig.as_ref().borrow_mut(); @@ -476,7 +478,7 @@ pub fn cred_setpolicy( } pub fn json_wildcard( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, action: InputAction, @@ -521,7 +523,7 @@ pub fn json_wildcard( } pub fn cmd_whitelist_action( - rconfig: &Rc>, + rconfig: &Rc>, role_id: String, task_id: IdTask, cmd_id: Vec, @@ -580,10 +582,10 @@ pub fn cmd_whitelist_action( } pub fn cmd_setpolicy( - rconfig: &Rc>, + rconfig: &Rc>, role_id: String, task_id: IdTask, - cmd_policy: crate::common::database::structs::SetBehavior, + cmd_policy: rar_common::database::structs::SetBehavior, ) -> Result> { debug!("chsr role r1 task t1 command setpolicy"); let config = rconfig.as_ref().borrow_mut(); @@ -597,7 +599,7 @@ pub fn cmd_setpolicy( } pub fn env_set_policylist( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, options_env: LinkedHashSet, @@ -623,10 +625,10 @@ pub fn env_set_policylist( } pub fn set_privileged( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, - options_root: crate::common::database::options::SPrivileged, + options_root: rar_common::database::options::SPrivileged, ) -> Result> { debug!("chsr o root set privileged"); perform_on_target_opt(rconfig, role_id, task_id, |opt: Rc>| { @@ -637,10 +639,10 @@ pub fn set_privileged( } pub fn set_bounding( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, - options_bounding: crate::common::database::options::SBounding, + options_bounding: rar_common::database::options::SBounding, ) -> Result> { debug!("chsr o bounding set"); perform_on_target_opt(rconfig, role_id, task_id, |opt: Rc>| { @@ -651,10 +653,10 @@ pub fn set_bounding( } pub fn set_authentication( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, - options_auth: crate::common::database::options::SAuthentication, + options_auth: rar_common::database::options::SAuthentication, ) -> Result> { debug!("chsr o auth set"); perform_on_target_opt(rconfig, role_id, task_id, |opt: Rc>| { @@ -665,7 +667,7 @@ pub fn set_authentication( } pub fn path_set( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, setlist_type: Option, @@ -695,7 +697,7 @@ pub fn path_set( } pub fn path_purge( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, setlist_type: Option, @@ -720,7 +722,7 @@ pub fn path_purge( } pub fn env_whitelist_set( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, setlist_type: Option, @@ -753,7 +755,7 @@ pub fn env_whitelist_set( } pub fn unset_timeout( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, timeout_arg: [bool; 3], @@ -782,10 +784,10 @@ pub fn unset_timeout( } pub fn set_timeout( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, - timeout_type: Option, + timeout_type: Option, timeout_duration: Option, timeout_max_usage: Option, ) -> Result> { @@ -808,7 +810,7 @@ pub fn set_timeout( } pub fn path_setlist2( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, setlist_type: Option, @@ -873,7 +875,7 @@ pub fn path_setlist2( } pub fn path_setpolicy( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, options_path_policy: PathBehavior, @@ -893,7 +895,7 @@ pub fn path_setpolicy( } pub fn env_setlist_add( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, setlist_type: Option, @@ -1026,7 +1028,7 @@ pub fn env_setlist_add( } pub fn env_setpolicy( - rconfig: &Rc>, + rconfig: &Rc>, role_id: Option, task_id: Option, options_env_policy: EnvBehavior, diff --git a/src/chsr/cli/usage.rs b/src/chsr/cli/usage.rs index 3af27183..47a20a3b 100644 --- a/src/chsr/cli/usage.rs +++ b/src/chsr/cli/usage.rs @@ -4,10 +4,8 @@ use const_format::formatcp; use tracing::debug; use super::data::Rule; -use crate::{ - common::util::{BOLD, RED, RST, UNDERLINE}, - util::underline, -}; +use crate::util::underline; +use rar_common::util::{BOLD, RED, RST, UNDERLINE}; const LONG_ABOUT: &str = "Role Manager is a tool to configure RBAC for RootAsRole. A role is a set of tasks that can be executed by a user or a group of users. diff --git a/src/chsr/main.rs b/src/chsr/main.rs index 660ba584..3c87eb33 100644 --- a/src/chsr/main.rs +++ b/src/chsr/main.rs @@ -1,30 +1,30 @@ //extern crate sudoers_reader; -use common::subsribe; -use common::{ - config::{self, Storage}, +use rar_common::{ + Storage, database::{read_json_config, save_json}, - drop_effective, + util::{ + drop_effective, read_effective, + subsribe, + }, plugin::register_plugins, - read_effective, + }; use tracing::{debug, error}; mod cli; -#[path = "../mod.rs"] -mod common; mod util; #[cfg(not(tarpaulin_include))] fn main() -> Result<(), Box> { - use common::config::ROOTASROLE; + use rar_common::{get_settings, StorageMethod, ROOTASROLE}; subsribe("chsr"); drop_effective()?; register_plugins(); - let settings = config::get_settings(ROOTASROLE).expect("Error on config read"); + let settings = get_settings(ROOTASROLE).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())?), + StorageMethod::JSON => Storage::JSON(read_json_config(settings.clone())?), _ => { error!("Unsupported storage method"); std::process::exit(1); diff --git a/src/chsr/util.rs b/src/chsr/util.rs index c9e1a87d..68e8521a 100644 --- a/src/chsr/util.rs +++ b/src/chsr/util.rs @@ -2,7 +2,7 @@ use std::mem; use pest::{error::LineColLocation, RuleType}; -use crate::common::util::escape_parser_string; +use rar_common::util::escape_parser_string; fn start(error: &pest::error::Error) -> (usize, usize) where diff --git a/src/mod.rs b/src/mod.rs deleted file mode 100644 index 4f0bca71..00000000 --- a/src/mod.rs +++ /dev/null @@ -1,167 +0,0 @@ -use capctl::{prctl, Cap, CapState}; -use serde::Serialize; -use std::{error::Error, ffi::CString, fs::File, path::Path}; -use tracing::{debug, Level}; -use tracing_subscriber::util::SubscriberInitExt; - -pub mod api; -pub mod config; -pub mod database; -pub mod util; -pub mod version; - -pub mod plugin; - -#[cfg(debug_assertions)] -pub fn subsribe(tool: &str) { - use std::io; - let identity = CString::new(tool).unwrap(); - let options = syslog_tracing::Options::LOG_PID; - let facility = syslog_tracing::Facility::Auth; - let _syslog = syslog_tracing::Syslog::new(identity, options, facility).unwrap(); - tracing_subscriber::fmt() - .with_max_level(Level::DEBUG) - .with_file(true) - .with_line_number(true) - .with_writer(io::stdout) - .finish() - .init(); -} - -#[cfg(not(debug_assertions))] -pub fn subsribe(tool: &str) { - use std::panic::set_hook; - - let identity = CString::new(tool).unwrap(); - let options = syslog_tracing::Options::LOG_PID; - let facility = syslog_tracing::Facility::Auth; - let syslog = syslog_tracing::Syslog::new(identity, options, facility).unwrap(); - tracing_subscriber::fmt() - .compact() - .with_max_level(Level::WARN) - .with_file(false) - .with_timer(false) - .with_line_number(false) - .with_target(false) - .without_time() - .with_writer(syslog) - .finish() - .init(); - set_hook(Box::new(|info| { - if let Some(s) = info.payload().downcast_ref::() { - println!("{}", s); - } - })); -} - -pub fn drop_effective() -> Result<(), capctl::Error> { - let mut current = CapState::get_current()?; - current.effective.clear(); - current.set_current() -} - -pub fn cap_effective(cap: Cap, enable: bool) -> Result<(), capctl::Error> { - let mut current = CapState::get_current()?; - current.effective.set_state(cap, enable); - current.set_current() -} - -pub fn setpcap_effective(enable: bool) -> Result<(), capctl::Error> { - cap_effective(Cap::SETPCAP, enable) -} - -pub fn setuid_effective(enable: bool) -> Result<(), capctl::Error> { - cap_effective(Cap::SETUID, enable) -} - -pub fn setgid_effective(enable: bool) -> Result<(), capctl::Error> { - cap_effective(Cap::SETGID, enable) -} - -pub fn fowner_effective(enable: bool) -> Result<(), capctl::Error> { - cap_effective(Cap::FOWNER, enable) -} - -pub fn read_effective(enable: bool) -> Result<(), capctl::Error> { - cap_effective(Cap::DAC_READ_SEARCH, enable) -} - -pub fn dac_override_effective(enable: bool) -> Result<(), capctl::Error> { - cap_effective(Cap::DAC_OVERRIDE, enable) -} - -pub fn immutable_effective(enable: bool) -> Result<(), capctl::Error> { - cap_effective(Cap::LINUX_IMMUTABLE, enable) -} - -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> -where - S: std::convert::AsRef + Clone, -{ - let file = create_with_privileges(path)?; - serde_json::to_writer_pretty(file, &settings)?; - Ok(()) -} - -pub fn create_with_privileges>(p: P) -> Result { - std::fs::File::create(&p).or_else(|e| { - debug!( - "Error creating file without privilege, trying with privileges: {}", - e - ); - dac_override_effective(true)?; - let res = std::fs::File::create(p).inspect_err(|e| { - debug!( - "Error creating file without privilege, trying with privileges: {}", - e - ); - }); - dac_override_effective(false)?; - res - }) -} - -pub fn open_with_privileges>(p: P) -> Result { - std::fs::File::open(&p).or_else(|e| { - debug!( - "Error creating file without privilege, trying with privileges: {}", - e - ); - read_effective(true).or(dac_override_effective(true))?; - let res = std::fs::File::open(p); - read_effective(false)?; - dac_override_effective(false)?; - res - }) -} - -pub fn remove_with_privileges>(p: P) -> Result<(), std::io::Error> { - std::fs::remove_file(&p).or_else(|e| { - debug!( - "Error creating file without privilege, trying with privileges: {}", - e - ); - dac_override_effective(true)?; - let res = std::fs::remove_file(p); - dac_override_effective(false)?; - res - }) -} - -pub fn create_dir_all_with_privileges>(p: P) -> Result<(), std::io::Error> { - std::fs::create_dir_all(&p).or_else(|e| { - debug!( - "Error creating file without privilege, trying with privileges: {}", - e - ); - dac_override_effective(true)?; - let res = std::fs::create_dir_all(p); - read_effective(false)?; - dac_override_effective(false)?; - res - }) -} diff --git a/src/sr/main.rs b/src/sr/main.rs index bf2e34bd..908747a9 100644 --- a/src/sr/main.rs +++ b/src/sr/main.rs @@ -1,12 +1,10 @@ -#[path = "../mod.rs"] -mod common; pub mod pam; mod timeout; use capctl::CapState; -use common::database::finder::{Cred, FilterMatcher, TaskMatch, TaskMatcher}; -use common::database::{options::OptStack, structs::SConfig}; -use common::util::escape_parser_string; +use rar_common::database::finder::{Cred, FilterMatcher, TaskMatch, TaskMatcher}; +use rar_common::database::{options::OptStack, structs::SConfig}; +use rar_common::util::escape_parser_string; use const_format::formatcp; use nix::{ libc::dev_t, @@ -21,16 +19,13 @@ use std::panic::set_hook; use std::{cell::RefCell, error::Error, io::stdout, os::fd::AsRawFd, rc::Rc}; use tracing::{debug, error}; -use crate::common::plugin::register_plugins; -use crate::common::{ - activates_no_new_privs, - config::{self, Storage}, - dac_override_effective, +use rar_common::plugin::register_plugins; +use rar_common::{ + util::{dac_override_effective,activates_no_new_privs, setgid_effective, setpcap_effective, setuid_effective, + drop_effective, read_effective, subsribe, BOLD, RST, UNDERLINE}, + self, Storage, database::{read_json_config, structs::SGroups}, - read_effective, setgid_effective, setpcap_effective, setuid_effective, - util::{BOLD, RST, UNDERLINE}, }; -use crate::common::{drop_effective, subsribe}; //const ABOUT: &str = "Execute privileged commands with a role-based access control system"; //const LONG_ABOUT: &str = @@ -184,7 +179,7 @@ where #[cfg(not(tarpaulin_include))] fn main() -> Result<(), Box> { - use crate::{common::config::ROOTASROLE, pam::check_auth}; + use crate::{rar_common::ROOTASROLE, pam::check_auth}; subsribe("sr"); drop_effective()?; @@ -198,12 +193,12 @@ fn main() -> Result<(), Box> { read_effective(true) .or(dac_override_effective(true)) .unwrap_or_else(|_| panic!("{}", cap_effective_error("dac_read_search or dac_override"))); - let settings = config::get_settings(ROOTASROLE).expect("Failed to get settings"); + let settings = rar_common::get_settings(ROOTASROLE).expect("Failed to get settings"); read_effective(false) .and(dac_override_effective(false)) .unwrap_or_else(|_| panic!("{}", cap_effective_error("dac_read"))); let config = match settings.clone().as_ref().borrow().storage.method { - config::StorageMethod::JSON => { + rar_common::StorageMethod::JSON => { Storage::JSON(read_json_config(settings).expect("Failed to read config")) } _ => { @@ -326,7 +321,7 @@ fn make_cred() -> Cred { user } -fn set_capabilities(execcfg: &common::database::finder::ExecSettings, optstack: &OptStack) { +fn set_capabilities(execcfg: &rar_common::database::finder::ExecSettings, optstack: &OptStack) { //set capabilities if let Some(caps) = execcfg.caps { // case where capabilities are more than bounding set @@ -360,7 +355,7 @@ fn set_capabilities(execcfg: &common::database::finder::ExecSettings, optstack: } } -fn setuid_setgid(execcfg: &common::database::finder::ExecSettings) { +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); if let Some(user) = res { @@ -419,10 +414,11 @@ fn setuid_setgid(execcfg: &common::database::finder::ExecSettings) { mod tests { use libc::getgid; use nix::unistd::Pid; + use rar_common::rc_refcell; use super::*; - use crate::common::database::make_weak_config; - use crate::common::database::structs::{ + use rar_common::database::make_weak_config; + use rar_common::database::structs::{ IdTask, SActor, SCommand, SCommands, SConfig, SRole, STask, }; diff --git a/src/sr/pam/mod.rs b/src/sr/pam/mod.rs index bbb7f440..ff2daba4 100644 --- a/src/sr/pam/mod.rs +++ b/src/sr/pam/mod.rs @@ -8,13 +8,11 @@ use pam_client::{Context, ConversationHandler, ErrorCode, Flag}; use pcre2::bytes::RegexBuilder; use tracing::{debug, error, info, warn}; -use crate::{ - common::{ - config::Storage, - database::{finder::Cred, options::OptStack}, - }, - timeout, +use rar_common::{ + Storage, + database::{finder::Cred, options::OptStack}, }; +use crate::timeout; use self::rpassword::Terminal; diff --git a/src/sr/timeout.rs b/src/sr/timeout.rs index b31a6072..32e53c2b 100644 --- a/src/sr/timeout.rs +++ b/src/sr/timeout.rs @@ -15,13 +15,12 @@ use nix::{ use serde::{Deserialize, Serialize}; use tracing::debug; -use crate::common::{ - create_dir_all_with_privileges, create_with_privileges, +use rar_common::{ + util::{create_dir_all_with_privileges, create_with_privileges,open_with_privileges, remove_with_privileges}, database::{ finder::Cred, options::{STimeout, TimestampType}, }, - open_with_privileges, remove_with_privileges, }; /// This module checks the validity of a user's credentials diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml index 9a988a90..58682653 100644 --- a/xtask/Cargo.toml +++ b/xtask/Cargo.toml @@ -5,5 +5,8 @@ version = "3.0.0-alpha.5" edition = "2021" [dependencies] -anyhow = "1" -clap = { version = "4.1", features = ["derive"] } +rar-common = { path = "../rar-common" } +anyhow = "1.0.86" +clap = { version = "4.5.16", features = ["derive"] } +serde = { version = "1.0.209", features = ["rc"] } +serde_json = "1.0.127" diff --git a/xtask/src/install.rs b/xtask/src/install.rs new file mode 100644 index 00000000..b7bd568b --- /dev/null +++ b/xtask/src/install.rs @@ -0,0 +1,123 @@ +use std::fs::{self, File}; +use std::io::{self, BufRead, BufReader}; +use std::path::Path; + +use serde::{Deserialize, Serialize}; +use serde_json::Value; + + +pub fn post_install() -> Result<(), anyhow::Error> { + check_config_file()?; + check_filesystem()?; + + + Ok(()) +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +struct SettingsFile { + pub storage: Settings, + #[serde(default)] + #[serde(flatten, skip)] + pub _extra_fields: Value, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Settings { + #[serde(skip_serializing_if = "Option::is_none")] + pub settings: Option, + #[serde(default)] + #[serde(flatten)] + pub _extra_fields: Value, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct RemoteStorageSettings { + #[serde(skip_serializing_if = "Option::is_none")] + pub immutable: Option, + #[serde(default)] + #[serde(flatten)] + pub _extra_fields: Value, +} + +const CONFIG_FILE: &str = "/etc/security/rootasrole.json"; + +fn check_filesystem() -> io::Result<()> { + let config = BufReader::new(File::open(CONFIG_FILE)?); + let mut config: SettingsFile = serde_json::from_reader(config)?; + // Get the filesystem type + if let Some(fs_type) = get_filesystem_type(CONFIG_FILE)? { + match fs_type.as_str() { + "ext2"|"ext3"|"ext4"|"xfs"|"btrfs"|"ocfs2"|"jfs"|"reiserfs" => { + set_immutable(&mut config, true); + } + _ => { + set_immutable(&mut config, false); + } + } + } else { + set_immutable(&mut config, false); + } + Ok(()) +} + +fn set_immutable(config: &mut SettingsFile, value: bool) { + if let Some(settings) = config.storage.settings.as_mut() { + if let Some(mut immutable) = settings.immutable { + immutable = value; + } + } +} + +fn get_filesystem_type>(path: P) -> io::Result> { + let path = path.as_ref(); + let mounts_file = File::open("/proc/mounts")?; + let reader = BufReader::new(mounts_file); + let mut longest_mount_point = String::new(); + let mut filesystem_type = None; + + for line_result in reader.lines() { + if let Ok(line_result) = line_result { + let fields: Vec<&str> = line_result.split_whitespace().collect(); + if fields.len() > 2 { + let mount_point = fields[1]; + let fs_type = fields[2]; + if path.starts_with(mount_point) && mount_point.len() > longest_mount_point.len() { + longest_mount_point = mount_point.to_string(); + filesystem_type = Some(fs_type.to_string()); + } + } + } else { + return Err(line_result.unwrap_err()); + } + } + + Ok(filesystem_type) +} + +fn check_config_file() -> io::Result<()> { + let default_path = "/usr/share/rootasrole/default.json"; + + // Check if the target file exists + if !Path::new(CONFIG_FILE).exists() { + // If the target file does not exist, copy the default file + if let Err(e) = fs::copy(default_path, CONFIG_FILE) { + eprintln!("Failed to copy the default configuration file to {}: {}", CONFIG_FILE, e); + std::process::exit(1); + } + } else { + // If the target file exists, compare it with the default file + if !files_are_equal(default_path, CONFIG_FILE)? { + std::process::exit(0); + } + } + + Ok(()) +} + +fn files_are_equal(path1: &str, path2: &str) -> io::Result { + let file1_content = fs::read(path1)?; + let file2_content = fs::read(path2)?; + + Ok(file1_content == file2_content) +} \ No newline at end of file diff --git a/xtask/src/main.rs b/xtask/src/main.rs index c1c594e0..b207ca45 100644 --- a/xtask/src/main.rs +++ b/xtask/src/main.rs @@ -1,5 +1,6 @@ mod build_ebpf; mod run; +mod install; use std::process::exit; @@ -15,6 +16,7 @@ pub struct Options { enum Command { BuildEbpf(build_ebpf::Options), Run(run::Options), + PostInstall, } fn main() { @@ -24,6 +26,7 @@ fn main() { let ret = match opts.command { BuildEbpf(opts) => build_ebpf::build_ebpf(opts), Run(opts) => run::run(opts), + PostInstall => install::post_install(), }; if let Err(e) = ret {